diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6f0214ba7..5ee5b1dd7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,6 +13,7 @@ There are a few ways to contribute: Please follow the steps below to build the IDE. If you have any questions, feel free to [submit an issue](https://github.com/voideditor/void/issues/new) with any build errors, or refer to VSCode's full [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute) page. +Most of Void's code lives in `src/vs/workbench/contrib/void/browser/` and `src/vs/platform/void/`. ### a. Build Prerequisites - Mac @@ -69,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. @@ -83,22 +84,25 @@ 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, you should probably just build (above). If you're sure you want 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` -## Roadmap - -Here are the most important topics on our Roadmap. More ⭐'s = more important. Please refer to our [Issues](https://github.com/voideditor/void/issues) page for the latest issues. - -## ⭐⭐⭐ Make History work well. +### Linux +- `npm run gulp vscode-linux-x64` - most common +- `npm run gulp vscode-linux-arm` +- `npm run gulp vscode-linux-ia32` -When the user submits a response or presses the apply/accept/reject button, we should add these events to the history, allowing the user to undo/redo them. Right now there is unexpected behavior if the user tries to undo or redo their changes. +## Roadmap -## ⭐⭐⭐ Build Cursor-style quick edits (Ctrl+K). +Please refer to our [Issues](https://github.com/voideditor/void/issues) page for the latest issues. -When the user presses Ctrl+K, an input box should appear inline with the code that they were selecting. This is somewhat difficult to do because an extension alone cannot do this, and it requires creating a new component in the IDE. We think you can modify vscode's built-in "codelens" or "zone widget" components, but we are open to alternatives. ## ⭐⭐⭐ Creative. @@ -106,10 +110,6 @@ Examples: creating better code search, or supporting AI agents that can edit acr Eventually, we want to build a convenient API for creating AI tools. The API will provide methods for creating the UI (showing an autocomplete suggestion, or creating a new diff), detecting event changes (like `onKeystroke` or `onFileOpen`), and modifying the user's file-system (storing indexes associated with each file), making it much easier to make your own AI plugin. We plan on building these features further along in timeline, but we wanted to list them for completeness. -## ⭐ One-stars. - -⭐ Let the user Accept / Reject all Diffs in an entire file via the sidebar. - # Guidelines We're always glad to talk about new ideas, help you get set up, and make sure your changes align with our vision for the project. Feel free to shoot us a message in the #general channel of the [Discord](https://discord.gg/RSNjgaugJs) for any reason. Please check in especially if you want to make a lot of changes or build a large new feature. diff --git a/VOID_USEFUL_LINKS.md b/VOID_USEFUL_LINKS.md index 52702d0ff..b3ba5a30a 100644 --- a/VOID_USEFUL_LINKS.md +++ b/VOID_USEFUL_LINKS.md @@ -2,18 +2,22 @@ The Void team put together this list of links to get up and running with VSCode's sourcecode. We hope it's helpful! +## Contributing + +- [How VSCode's sourcecode is organized](https://github.com/microsoft/vscode/wiki/Source-Code-Organization) - this explains where the entry point files are, what `browser/` and `common/` mean, etc. This is the most important read on this whole list! We recommend reading the whole thing. + +- [Built-in VSCode styles](https://code.visualstudio.com/api/references/theme-color) - CSS variables that are built into VSCode. Use `var(--vscode-{theme but replacing . with -})`. You can also see their [Webview theming guide](https://code.visualstudio.com/api/extension-guides/webview#theming-webview-content). + ## Beginners / Getting started - [VSCode UI guide](https://code.visualstudio.com/docs/getstarted/userinterface) - covers auxbar, panels, etc. - [UX guide](https://code.visualstudio.com/api/ux-guidelines/overview) - covers Containers, Views, Items, etc. -## Contributing - -- [How VSCode's sourcecode is organized](https://github.com/microsoft/vscode/wiki/Source-Code-Organization) - this explains where the entry point files are, what `browser/` and `common/` mean, etc. This is the most important read on this whole list! We recommend reading the whole thing. +## Misc -- [Every command](https://code.visualstudio.com/api/references/commands) built-in to VSCode - sometimes useful to reference. +- [Every command](https://code.visualstudio.com/api/references/commands) built-in to VSCode - not used often, but here for reference. ## VSCode's Extension API diff --git a/package-lock.json b/package-lock.json index afc964f6d..82df8eedb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,9 +10,13 @@ "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", + "@rrweb/record": "^2.0.0-alpha.17", + "@rrweb/types": "^2.0.0-alpha.17", "@vscode/deviceid": "^0.1.1", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/policy-watcher": "^1.1.4", @@ -35,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", @@ -46,7 +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", @@ -56,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", @@ -103,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", @@ -147,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", @@ -156,14 +163,10 @@ "path-browserify": "^1.0.1", "postcss": "^8.4.33", "postcss-nesting": "^12.0.2", - "posthog-js": "^1.184.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", @@ -217,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", @@ -233,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" @@ -1001,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" @@ -1682,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" @@ -2599,6 +2598,31 @@ "win32" ] }, + "node_modules/@rrweb/record": { + "version": "2.0.0-alpha.17", + "resolved": "https://registry.npmjs.org/@rrweb/record/-/record-2.0.0-alpha.17.tgz", + "integrity": "sha512-Je+lzjeWMF8/I0IDoXFzkGPKT8j7AkaBup5YcwUHlkp18VhLVze416MvI6915teE27uUA2ScXMXzG0Yiu5VTIw==", + "license": "MIT", + "dependencies": { + "@rrweb/types": "^2.0.0-alpha.17", + "rrweb": "^2.0.0-alpha.17" + } + }, + "node_modules/@rrweb/types": { + "version": "2.0.0-alpha.17", + "resolved": "https://registry.npmjs.org/@rrweb/types/-/types-2.0.0-alpha.17.tgz", + "integrity": "sha512-AfDTVUuCyCaIG0lTSqYtrZqJX39ZEYzs4fYKnexhQ+id+kbZIpIJtaut5cto6dWZbB3SEe4fW0o90Po3LvTmfg==", + "license": "MIT", + "dependencies": { + "rrweb-snapshot": "^2.0.0-alpha.17" + } + }, + "node_modules/@rrweb/utils": { + "version": "2.0.0-alpha.17", + "resolved": "https://registry.npmjs.org/@rrweb/utils/-/utils-2.0.0-alpha.17.tgz", + "integrity": "sha512-HCsasPERBwOS9/LQeOytO2ETKTCqRj1wORBuxiy3t41hKhmi225DdrUPiWnyDdTQm1GdVbOymMRknJVPnZaSXw==", + "license": "MIT" + }, "node_modules/@sindresorhus/is": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", @@ -2972,6 +2996,12 @@ "integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==", "dev": true }, + "node_modules/@types/css-font-loading-module": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.7.tgz", + "integrity": "sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==", + "license": "MIT" + }, "node_modules/@types/debug": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.9.tgz", @@ -2988,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", @@ -3027,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" @@ -3214,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": { @@ -4423,6 +4462,12 @@ } } }, + "node_modules/@xstate/fsm": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@xstate/fsm/-/fsm-1.6.5.tgz", + "integrity": "sha512-b5o1I6aLNeYlU/3CPlj/Z91ybk1gUsKT+5NAJI+2W4UjvS5KLG28K9v5UvNoFVjHV8PajVZ00RH3vnjyQO7ZAw==", + "license": "MIT" + }, "node_modules/@xterm/addon-clipboard": { "version": "0.2.0-beta.47", "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.47.tgz", @@ -5208,6 +5253,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/axios": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/b4a": { "version": "1.6.4", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", @@ -5316,6 +5372,15 @@ "node": ">=0.10.0" } }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -5791,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", @@ -5802,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", @@ -5813,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", @@ -6303,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", @@ -6572,18 +6633,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/core-js": { - "version": "3.39.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.39.0.tgz", - "integrity": "sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -7201,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" @@ -8450,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" @@ -8483,13 +8530,6 @@ } } }, - "node_modules/fflate": { - "version": "0.4.8", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", - "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==", - "dev": true, - "license": "MIT" - }, "node_modules/figures": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", @@ -8892,6 +8932,26 @@ "util-deprecate": "~1.0.1" } }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -8961,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" } @@ -11969,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", @@ -11980,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", @@ -12007,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": "*" @@ -12017,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": { @@ -12663,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", @@ -12674,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", @@ -12886,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", @@ -13013,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", @@ -13551,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", @@ -14232,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" @@ -14254,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", @@ -14817,6 +14865,12 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, "node_modules/mixin-deep": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", @@ -15918,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" @@ -15994,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", @@ -16024,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" @@ -16334,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", @@ -16579,7 +16629,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -16825,7 +16874,6 @@ "version": "8.4.48", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.48.tgz", "integrity": "sha512-GCRK8F6+Dl7xYniR5a4FYbpBzU8XnZVeowqsQFYdcXuSbChgiks7qybSkbvnaeqv0G0B+dd9/jJgH8kkLDQeEA==", - "dev": true, "funding": [ { "type": "opencollective", @@ -17559,7 +17607,6 @@ "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, "funding": [ { "type": "github", @@ -17573,28 +17620,17 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/posthog-js": { - "version": "1.184.2", - "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.184.2.tgz", - "integrity": "sha512-n7OxFntH7m4sw/GsuUAliWGqBDV7g7IemvjwGISCQFQpf5cSqZgmvT2V2O3GHXpONiEgwL/Ud/+zWmhZbWLExg==", - "dev": true, + "node_modules/posthog-node": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-4.3.1.tgz", + "integrity": "sha512-By9SEGZxBLC7GVyVb+HlJlpxM/xI4iLUgwtsBS8f4bZ0wqYKiNHoYcFEwMJnTM9xQcQZztr6dqLmsJ7jCv0vxA==", "license": "MIT", "dependencies": { - "core-js": "^3.38.1", - "fflate": "^0.4.8", - "preact": "^10.19.3", - "web-vitals": "^4.2.0" - } - }, - "node_modules/preact": { - "version": "10.24.3", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz", - "integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" + "axios": "^1.7.4", + "rusha": "^0.8.14" + }, + "engines": { + "node": ">=15.0.0" } }, "node_modules/prebuild-install": { @@ -17684,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" @@ -17718,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" @@ -17909,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" @@ -17922,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", @@ -17936,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", @@ -18154,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", @@ -18170,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" @@ -18180,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": { @@ -18676,6 +18704,40 @@ "fsevents": "~2.3.2" } }, + "node_modules/rrdom": { + "version": "2.0.0-alpha.17", + "resolved": "https://registry.npmjs.org/rrdom/-/rrdom-2.0.0-alpha.17.tgz", + "integrity": "sha512-b6caDiNcFO96Opp7TGdcVd4OLGSXu5dJe+A0IDiAu8mk7OmhqZCSDlgQdTKmdO5wMf4zPsUTgb8H/aNvR3kDHA==", + "license": "MIT", + "dependencies": { + "rrweb-snapshot": "^2.0.0-alpha.17" + } + }, + "node_modules/rrweb": { + "version": "2.0.0-alpha.17", + "resolved": "https://registry.npmjs.org/rrweb/-/rrweb-2.0.0-alpha.17.tgz", + "integrity": "sha512-GQxBkCC4r9XL2bwSdv7iIS49M3cEA8OtObVq0rrQ4GUT4+h7omucGQ4x7m5YN5Vq1oalStBaBlYqF7yRnfG3JA==", + "license": "MIT", + "dependencies": { + "@rrweb/types": "^2.0.0-alpha.17", + "@rrweb/utils": "^2.0.0-alpha.17", + "@types/css-font-loading-module": "0.0.7", + "@xstate/fsm": "^1.4.0", + "base64-arraybuffer": "^1.0.1", + "mitt": "^3.0.0", + "rrdom": "^2.0.0-alpha.17", + "rrweb-snapshot": "^2.0.0-alpha.17" + } + }, + "node_modules/rrweb-snapshot": { + "version": "2.0.0-alpha.17", + "resolved": "https://registry.npmjs.org/rrweb-snapshot/-/rrweb-snapshot-2.0.0-alpha.17.tgz", + "integrity": "sha512-GBg5pV8LHOTbeVmH2VHLEFR0mc2QpQMzAvcoxEGfPNWgWHc8UvKCyq7pqN1vA+fDZ+yXXbixeO0kB2pzVvFCBw==", + "license": "MIT", + "dependencies": { + "postcss": "^8.4.38" + } + }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -18705,6 +18767,12 @@ } ] }, + "node_modules/rusha": { + "version": "0.8.14", + "resolved": "https://registry.npmjs.org/rusha/-/rusha-0.8.14.tgz", + "integrity": "sha512-cLgakCUf6PedEu15t8kbsjnwIFFR2D4RfL+W3iWFJ4iac7z4B0ZI8fxy4R3J956kAI68HclCFGL8MPoUVC3qVA==", + "license": "MIT" + }, "node_modules/rxjs": { "version": "6.6.3", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz", @@ -18797,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" @@ -18857,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": { @@ -19603,7 +19670,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -19652,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", @@ -22287,13 +22352,6 @@ "integrity": "sha512-weOVgZ3aAARgdnb220GqYuh7+rZU0Ka9k9yfKtGAzEYMa6GgiCzW9JjQRJyCJakvibQW+dfjJdihjInKuuCAUQ==", "dev": true }, - "node_modules/web-vitals": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz", - "integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -22558,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": { @@ -22792,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 968a9b1d9..409cf26c1 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "main": "./out/main", "private": true, "scripts": { + "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", @@ -72,9 +73,13 @@ "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", + "@rrweb/record": "^2.0.0-alpha.17", + "@rrweb/types": "^2.0.0-alpha.17", "@vscode/deviceid": "^0.1.1", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/policy-watcher": "^1.1.4", @@ -97,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", @@ -108,7 +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", @@ -118,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", @@ -165,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", @@ -209,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", @@ -218,14 +226,10 @@ "path-browserify": "^1.0.1", "postcss": "^8.4.33", "postcss-nesting": "^12.0.2", - "posthog-js": "^1.184.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/main.js b/src/main.js index 10bd5033d..03391f64d 100644 --- a/src/main.js +++ b/src/main.js @@ -123,7 +123,7 @@ protocol.registerSchemesAsPrivileged([ }, { scheme: 'vscode-file', - privileges: { secure: true, standard: true, supportFetchAPI: true, corsEnabled: true, codeCache: true } + privileges: { secure: true, standard: true, supportFetchAPI: true, corsEnabled: true, codeCache: true, } } ]); diff --git a/src/tsconfig.json b/src/tsconfig.json index 36fa85083..3e110f9af 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -52,10 +52,10 @@ "./typings", "./vs/**/*.ts", "vscode-dts/vscode.proposed.*.d.ts", - "vscode-dts/vscode.d.ts" + "vscode-dts/vscode.d.ts", // Void added these: - // "./vs/**/*.tsx", + // "./vs/workbench/contrib/void/browser/**.tsx", // "./vs/**/*.d.mts", ] } diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 59e8af61d..1a0270001 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -122,6 +122,8 @@ import { ICSSDevelopmentService, CSSDevelopmentService } from '../../platform/cs 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'; /** * The main VS Code application. There will only ever be one instance, @@ -1103,6 +1105,9 @@ export class CodeApplication extends Disposable { services.set(ITelemetryService, NullTelemetryService); } + // Void main process services (required for services with a channel for comm between browser and electron-main (node)) + services.set(IMetricsService, new SyncDescriptor(MetricsMainService, undefined, false)); + // Default Extensions Profile Init services.set(IExtensionsProfileScannerService, new SyncDescriptor(ExtensionsProfileScannerService, undefined, true)); services.set(IExtensionsScannerService, new SyncDescriptor(ExtensionsScannerService, undefined, true)); @@ -1237,10 +1242,11 @@ export class CodeApplication extends Disposable { mainProcessElectronServer.registerChannel('logger', loggerChannel); sharedProcessClient.then(client => client.registerChannel('logger', loggerChannel)); - // Void - // const sendLLMMessageChannel = ProxyChannel.fromService(accessor.get(ISendLLMMessageService), disposables); - const sendLLMMessageChannel = new LLMMessageChannel(); - mainProcessElectronServer.registerChannel('void-channel-sendLLMMessage', sendLLMMessageChannel); + // Void - use loggerChannel as reference + const metricsChannel = ProxyChannel.fromService(accessor.get(IMetricsService), disposables); + mainProcessElectronServer.registerChannel('void-channel-metrics', metricsChannel); + const llmMessageChannel = new LLMMessageChannel(accessor.get(IMetricsService)); + mainProcessElectronServer.registerChannel('void-channel-llmMessageService', llmMessageChannel); // Extension Host Debug Broadcasting const electronExtensionHostDebugBroadcastChannel = new ElectronExtensionHostDebugBroadcastChannel(accessor.get(IWindowsMainService)); diff --git a/src/vs/platform/void/browser/llmMessageService.ts b/src/vs/platform/void/browser/llmMessageService.ts deleted file mode 100644 index 61f51ed82..000000000 --- a/src/vs/platform/void/browser/llmMessageService.ts +++ /dev/null @@ -1,105 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Glass Devtools, Inc. All rights reserved. - * Void Editor additions licensed under the AGPLv3 License. - *--------------------------------------------------------------------------------------------*/ - -import { ProxyOnTextPayload, ProxyOnErrorPayload, ProxyOnFinalMessagePayload, LLMMessageServiceParams, ProxyLLMMessageParams, ProxyLLMMessageAbortParams } from '../common/llmMessageTypes.js'; -import { IChannel } from '../../../base/parts/ipc/common/ipc.js'; -import { IMainProcessService } from '../../ipc/common/mainProcessService.js'; -import { InstantiationType, registerSingleton } from '../../instantiation/common/extensions.js'; -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'; - - -// BROWSER IMPLEMENTATION OF SENDLLMMESSAGE -export const ISendLLMMessageService = createDecorator('sendLLMMessageService'); - -// defines an interface that node/ creates and browser/ uses -export interface ISendLLMMessageService { - readonly _serviceBrand: undefined; - sendLLMMessage: (params: LLMMessageServiceParams) => string; - abort: (requestId: string) => void; -} - - -export class SendLLMMessageService implements ISendLLMMessageService { - - readonly _serviceBrand: undefined; - private readonly channel: IChannel; - - private readonly _disposablesOfRequestId: Record = {} - - constructor( - @IMainProcessService mainProcessService: IMainProcessService // used as a renderer (only usable on client side) - ) { - - 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 - } - - _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; - - // listen for listenerName='onText' | 'onFinalMessage' | 'onError', and call the original function on it - - const onTextEvent: Event = this.channel.listen('onText') - this._addDisposable(requestId_, - onTextEvent(e => { - if (requestId_ !== e.requestId) return; - onText(e) - }) - ) - - const onFinalMessageEvent: Event = this.channel.listen('onFinalMessage') - this._addDisposable(requestId_, - onFinalMessageEvent(e => { - if (requestId_ !== e.requestId) return; - onFinalMessage(e) - this._dispose(requestId_) - }) - ) - - 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); - - 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) - } -} - -registerSingleton(ISendLLMMessageService, SendLLMMessageService, InstantiationType.Delayed); - diff --git a/src/vs/platform/void/common/llmMessageService.ts b/src/vs/platform/void/common/llmMessageService.ts new file mode 100644 index 000000000..967b5a9f5 --- /dev/null +++ b/src/vs/platform/void/common/llmMessageService.ts @@ -0,0 +1,146 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Glass Devtools, Inc. All rights reserved. + * Void Editor additions licensed under the AGPL 3.0 License. + *--------------------------------------------------------------------------------------------*/ + +import { EventLLMMessageOnTextParams, EventLLMMessageOnErrorParams, EventLLMMessageOnFinalMessageParams, ServiceSendLLMMessageParams, MainLLMMessageParams, MainLLMMessageAbortParams, ServiceOllamaListParams, EventOllamaListOnSuccessParams, EventOllamaListOnErrorParams, MainOllamaListParams } from './llmMessageTypes.js'; +import { IChannel } from '../../../base/parts/ipc/common/ipc.js'; +import { IMainProcessService } from '../../ipc/common/mainProcessService.js'; +import { InstantiationType, registerSingleton } from '../../instantiation/common/extensions.js'; +import { generateUuid } from '../../../base/common/uuid.js'; +import { createDecorator } from '../../instantiation/common/instantiation.js'; +import { Event } from '../../../base/common/event.js'; +import { Disposable } from '../../../base/common/lifecycle.js'; +import { IVoidConfigStateService } from './voidConfigService.js'; +// import { INotificationService } from '../../notification/common/notification.js'; + +// calls channel to implement features +export const ILLMMessageService = createDecorator('llmMessageService'); + +export interface ILLMMessageService { + readonly _serviceBrand: undefined; + sendLLMMessage: (params: ServiceSendLLMMessageParams) => string | null; + abort: (requestId: string) => void; + ollamaList: (params: ServiceOllamaListParams) => void; +} + +export class LLMMessageService extends Disposable implements ILLMMessageService { + + readonly _serviceBrand: undefined; + private readonly channel: IChannel // LLMMessageChannel + + // llmMessage + private readonly onTextHooks_llm: { [eventId: string]: ((params: EventLLMMessageOnTextParams) => void) } = {} + private readonly onFinalMessageHooks_llm: { [eventId: string]: ((params: EventLLMMessageOnFinalMessageParams) => void) } = {} + private readonly onErrorHooks_llm: { [eventId: string]: ((params: EventLLMMessageOnErrorParams) => void) } = {} + + + // ollamaList + private readonly onSuccess_ollama: { [eventId: string]: ((params: EventOllamaListOnSuccessParams) => void) } = {} + private readonly onError_ollama: { [eventId: string]: ((params: EventOllamaListOnErrorParams) => void) } = {} + + + constructor( + @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() + + // const service = ProxyChannel.toService(mainProcessService.getChannel('void-channel-sendLLMMessage')); // lets you call it like a service + this.channel = this.mainProcessService.getChannel('void-channel-llmMessageService') + + // .listen sets up an IPC channel and takes a few ms, so we set up listeners immediately and add hooks to them instead + // llm + this._register((this.channel.listen('onText_llm') satisfies Event)(e => { + this.onTextHooks_llm[e.requestId]?.(e) + })) + this._register((this.channel.listen('onFinalMessage_llm') satisfies Event)(e => { + this.onFinalMessageHooks_llm[e.requestId]?.(e) + this._onRequestIdDone(e.requestId) + })) + this._register((this.channel.listen('onError_llm') satisfies Event)(e => { + console.log('Error in LLMMessageService:', JSON.stringify(e)) + this.onErrorHooks_llm[e.requestId]?.(e) + this._onRequestIdDone(e.requestId) + })) + // ollama + this._register((this.channel.listen('onSuccess_ollama') satisfies Event)(e => { + this.onSuccess_ollama[e.requestId]?.(e) + })) + this._register((this.channel.listen('onError_ollama') satisfies Event)(e => { + this.onError_ollama[e.requestId]?.(e) + })) + + } + + sendLLMMessage(params: ServiceSendLLMMessageParams) { + const { onText, onFinalMessage, onError, ...proxyParams } = params; + const { featureName } = proxyParams + + // end early if no provider + const modelSelection = this.voidConfigStateService.state.modelSelectionOfFeature[featureName] + if (modelSelection === null) { + onError({ message: 'Please add a Provider in Settings!', fullError: null }) + return null + } + const { providerName, modelName } = modelSelection + + // add state for request id + const requestId_ = generateUuid(); + this.onTextHooks_llm[requestId_] = onText + this.onFinalMessageHooks_llm[requestId_] = onFinalMessage + this.onErrorHooks_llm[requestId_] = onError + + const { settingsOfProvider } = this.voidConfigStateService.state + + // params will be stripped of all its functions over the IPC channel + this.channel.call('sendLLMMessage', { + ...proxyParams, + requestId: requestId_, + providerName, + modelName, + settingsOfProvider, + } satisfies MainLLMMessageParams); + + return requestId_ + } + + + abort(requestId: string) { + this.channel.call('abort', { requestId } satisfies MainLLMMessageAbortParams); + this._onRequestIdDone(requestId) + } + + + ollamaList = (params: ServiceOllamaListParams) => { + const { onSuccess, onError, ...proxyParams } = params + + const { settingsOfProvider } = this.voidConfigStateService.state + + // add state for request id + const requestId_ = generateUuid(); + this.onSuccess_ollama[requestId_] = onSuccess + this.onError_ollama[requestId_] = onError + + this.channel.call('ollamaList', { + ...proxyParams, + settingsOfProvider, + requestId: requestId_, + } satisfies MainOllamaListParams) + } + + + + _onRequestIdDone(requestId: string) { + delete this.onTextHooks_llm[requestId] + delete this.onFinalMessageHooks_llm[requestId] + delete this.onErrorHooks_llm[requestId] + + delete this.onSuccess_ollama[requestId] + delete this.onError_ollama[requestId] + } +} + +registerSingleton(ILLMMessageService, LLMMessageService, InstantiationType.Eager); + diff --git a/src/vs/platform/void/common/llmMessageTypes.ts b/src/vs/platform/void/common/llmMessageTypes.ts index 287335111..3db5d5957 100644 --- a/src/vs/platform/void/common/llmMessageTypes.ts +++ b/src/vs/platform/void/common/llmMessageTypes.ts @@ -1,18 +1,15 @@ /*--------------------------------------------------------------------------------------------- * 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 { VoidConfig } from '../../../workbench/contrib/void/browser/registerConfig.js'; +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 } export type LLMMessage = { @@ -20,39 +17,124 @@ export type LLMMessage = { content: string; } -export type LLMMessageServiceParams = { +export type LLMFeatureSelection = { + featureName: 'Ctrl+K', + range: IRange +} | { + featureName: 'Ctrl+L', +} | { + featureName: 'Autocomplete', + range: IRange +} + +// params to the true sendLLMMessage function +export type LLMMMessageParams = { onText: OnText; onFinalMessage: OnFinalMessage; onError: OnError; + abortRef: AbortRef; messages: LLMMessage[]; - voidConfig: VoidConfig | null; logging: { loggingName: string, }; + providerName: ProviderName; + modelName: string; + settingsOfProvider: SettingsOfProvider; } -export type SendLLMMMessageParams = { +export type ServiceSendLLMMessageParams = { onText: OnText; onFinalMessage: OnFinalMessage; onError: OnError; messages: LLMMessage[]; - voidConfig: VoidConfig | null; logging: { loggingName: string, }; - abortRef: AbortRef; -} +} & LLMFeatureSelection // 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 BlockedMainLLMMessageParams = 'onText' | 'onFinalMessage' | 'onError' | 'abortRef' + +export type MainLLMMessageParams = Omit & { requestId: string } +export type MainLLMMessageAbortParams = { requestId: string } + +export type EventLLMMessageOnTextParams = Parameters[0] & { requestId: string } +export type EventLLMMessageOnFinalMessageParams = Parameters[0] & { requestId: string } +export type EventLLMMessageOnErrorParams = Parameters[0] & { requestId: string } + +export type _InternalSendLLMMessageFnType = (params: { + messages: LLMMessage[]; + onText: OnText; + onFinalMessage: OnFinalMessage; + onError: OnError; + settingsOfProvider: SettingsOfProvider; + providerName: ProviderName; + modelName: string; + + _setAborter: (aborter: () => void) => void; +}) => void + +// service -> main -> internal -> event (back to main) +// (browser) + + + + + + + + + + + + + + + + +// These are from 'ollama' SDK +interface ModelDetails { + parent_model: string; + format: string; + family: string; + families: string[]; + parameter_size: string; + quantization_level: string; +} + +export type ModelResponse = { + name: string; + modified_at: Date; + size: number; + digest: string; + details: ModelDetails; + expires_at: Date; + size_vram: number; +} + + +// params to the true list fn +export type OllamaListParams = { + settingsOfProvider: SettingsOfProvider; + onSuccess: (param: { models: ModelResponse[] }) => void; + onError: (param: { error: string }) => void; +} + +export type ServiceOllamaListParams = { + onSuccess: (param: { models: ModelResponse[] }) => void; + onError: (param: { error: any }) => void; +} + +type BlockedMainOllamaListParams = 'onSuccess' | 'onError' +export type MainOllamaListParams = Omit & { requestId: string } + +export type EventOllamaListOnSuccessParams = Parameters[0] & { requestId: string } +export type EventOllamaListOnErrorParams = Parameters[0] & { 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 _InternalOllamaListFnType = (params: OllamaListParams) => void diff --git a/src/vs/platform/void/common/metricsService.ts b/src/vs/platform/void/common/metricsService.ts new file mode 100644 index 000000000..039697a08 --- /dev/null +++ b/src/vs/platform/void/common/metricsService.ts @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * 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'; +import { ProxyChannel } from '../../../base/parts/ipc/common/ipc.js'; +import { IMainProcessService } from '../../ipc/common/mainProcessService.js'; +import { InstantiationType, registerSingleton } from '../../instantiation/common/extensions.js'; + +export interface IMetricsService { + readonly _serviceBrand: undefined; + capture(event: string, params: Record): void; +} + +export const IMetricsService = createDecorator('metricsService'); + + +// implemented by calling channel +export class MetricsService implements IMetricsService { + + readonly _serviceBrand: undefined; + private readonly metricsService: IMetricsService; + + constructor( + @IMainProcessService mainProcessService: IMainProcessService // (only usable on client side) + ) { + this.metricsService = ProxyChannel.toService(mainProcessService.getChannel('void-channel-metrics')); + } + + // call capture on the channel + capture(...params: Parameters) { + this.metricsService.capture(...params); + } + +} + +registerSingleton(IMetricsService, MetricsService, InstantiationType.Eager); + diff --git a/src/vs/platform/void/common/refreshModelService.ts b/src/vs/platform/void/common/refreshModelService.ts new file mode 100644 index 000000000..a724d8ced --- /dev/null +++ b/src/vs/platform/void/common/refreshModelService.ts @@ -0,0 +1,106 @@ +/*--------------------------------------------------------------------------------------------- + * 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'; +import { InstantiationType, registerSingleton } from '../../instantiation/common/extensions.js'; +import { IVoidConfigStateService } from './voidConfigService.js'; +import { ILLMMessageService } from './llmMessageService.js'; +import { Emitter, Event } from '../../../base/common/event.js'; +import { Disposable } from '../../../base/common/lifecycle.js'; + + +export type RefreshModelState = 'done' | 'loading' + +// element-wise equals +function eq(a: T[], b: T[]): boolean { + if (a.length !== b.length) return false + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) return false + } + return true +} +export interface IRefreshModelService { + readonly _serviceBrand: undefined; + refreshOllamaModels(): void; + onDidChangeState: Event; + state: RefreshModelState; +} + +export const IRefreshModelService = createDecorator('RefreshModelService'); + +export class RefreshModelService extends Disposable implements IRefreshModelService { + + readonly _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 + + constructor( + @IVoidConfigStateService private readonly voidConfigStateService: IVoidConfigStateService, + @ILLMMessageService private readonly llmMessageService: ILLMMessageService, + ) { + super() + + // on mount, refresh ollama models + this.refreshOllamaModels() + + // every time ollama.enabled changes, refresh ollama models + let relevantVals = () => [this.voidConfigStateService.state.settingsOfProvider.ollama.enabled, this.voidConfigStateService.state.settingsOfProvider.ollama.endpoint] + let prevVals = relevantVals() + this._register( + this.voidConfigStateService.onDidChangeState(() => { // we might want to debounce this + const newVals = relevantVals() + if (!eq(prevVals, newVals)) { + this.refreshOllamaModels() + prevVals = newVals + } + }) + ) + + } + + state: RefreshModelState = 'done' + + private _timeoutId: NodeJS.Timeout | null = null + private _cancelTimeout = () => { + if (this._timeoutId) { + clearTimeout(this._timeoutId) + this._timeoutId = null + } + } + async refreshOllamaModels() { + // cancel any existing poll + this._cancelTimeout() + + // if ollama is disabled, obivously done + if (this.voidConfigStateService.state.settingsOfProvider.ollama.enabled !== 'true') { + this._setState('done') + return + } + + // start loading models + this._setState('loading') + + this.llmMessageService.ollamaList({ + onSuccess: ({ models }) => { + this.voidConfigStateService.setSettingOfProvider('ollama', 'models', models.map(model => model.name)) + this._setState('done') + }, + onError: ({ error }) => { + // poll + console.log('retrying ollamaList:', error) + this._timeoutId = setTimeout(() => this.refreshOllamaModels(), 5000) + } + }) + } + + private _setState(state: RefreshModelState) { + this.state = state + this._onDidChangeState.fire() + } +} + +registerSingleton(IRefreshModelService, RefreshModelService, InstantiationType.Eager); + diff --git a/src/vs/platform/void/common/void.contribution.ts b/src/vs/platform/void/common/void.contribution.ts new file mode 100644 index 000000000..2e12fb9cc --- /dev/null +++ b/src/vs/platform/void/common/void.contribution.ts @@ -0,0 +1,11 @@ +// llmMessage +import './llmMessageService.js' + +// voidConfig +import './voidConfigService.js' + +// refreshModel +import './refreshModelService.js' + +// metrics +import './metricsService.js' diff --git a/src/vs/platform/void/common/voidConfigModelDefaults.ts b/src/vs/platform/void/common/voidConfigModelDefaults.ts new file mode 100644 index 000000000..8d2fc0f25 --- /dev/null +++ b/src/vs/platform/void/common/voidConfigModelDefaults.ts @@ -0,0 +1,58 @@ + + +export const defaultAnthropicModels = [ + 'claude-3-5-sonnet-20240620', + // 'claude-3-opus-20240229', + // 'claude-3-sonnet-20240229', + // 'claude-3-haiku-20240307' +] + + +export const defaultOpenAIModels = [ + 'o1-preview', + 'o1-mini', + 'gpt-4o', + 'gpt-4o-mini', + // 'gpt-4o-2024-05-13', + // 'gpt-4o-2024-08-06', + // 'gpt-4o-mini-2024-07-18', + // 'gpt-4-turbo', + // 'gpt-4-turbo-2024-04-09', + // 'gpt-4-turbo-preview', + // 'gpt-4-0125-preview', + // 'gpt-4-1106-preview', + // 'gpt-4', + // 'gpt-4-0613', + // 'gpt-3.5-turbo-0125', + // 'gpt-3.5-turbo', + // 'gpt-3.5-turbo-1106', +] + + + +export const defaultGroqModels = [ + "mixtral-8x7b-32768", + "llama2-70b-4096", + "gemma-7b-it" +] + + +export const defaultGeminiModels = [ + 'gemini-1.5-flash', + 'gemini-1.5-pro', + 'gemini-1.5-flash-8b', + 'gemini-1.0-pro' +] + + + + + +export const dummyModelData = { + anthropic: ['claude 3.5'], + openAI: ['gpt 4o'], + ollama: ['llama 3.2', 'codestral'], + openRouter: ['qwen 2.5'], +} + + diff --git a/src/vs/platform/void/common/voidConfigService.ts b/src/vs/platform/void/common/voidConfigService.ts new file mode 100644 index 000000000..4b065f70d --- /dev/null +++ b/src/vs/platform/void/common/voidConfigService.ts @@ -0,0 +1,137 @@ +/*--------------------------------------------------------------------------------------------- + * 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, SettingName } from './voidConfigTypes.js'; + + +const STORAGE_KEY = 'void.voidConfigStateII' + +type SetSettingOfProviderFn = ( + providerName: ProviderName, + settingName: S, + newVal: SettingsOfProvider[ProviderName][S extends keyof SettingsOfProvider[ProviderName] ? S : never], +) => Promise; + +type SetModelSelectionOfFeature = ( + featureName: K, + newVal: ModelSelectionOfFeature[K], +) => Promise; + + +type VoidConfigState = { + readonly settingsOfProvider: SettingsOfProvider; // optionsOfProvider + readonly modelSelectionOfFeature: ModelSelectionOfFeature; // stateOfFeature +} + +export interface IVoidConfigStateService { + readonly _serviceBrand: undefined; + readonly state: VoidConfigState; + onDidChangeState: 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 VoidConfigService 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 + + 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) + }) + + } + + 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, settingName, newVal) => { + const newState: VoidConfigState = { + ...this.state, + settingsOfProvider: { + ...this.state.settingsOfProvider, + [providerName]: { + ...this.state.settingsOfProvider[providerName], + [settingName]: newVal, + } + }, + } + // console.log('NEW STATE I', JSON.stringify(newState, null, 2)) + + 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', JSON.stringify(newState, null, 2)) + + await this._storeVoidConfigState(newState) + this._setState(newState) + } + + + + // internal function to update state, should be called every time state changes + private async _setState(voidConfigState: VoidConfigState) { + this.state = voidConfigState + this._onDidChangeState.fire() + } + +} + +registerSingleton(IVoidConfigStateService, VoidConfigService, InstantiationType.Eager); diff --git a/src/vs/platform/void/common/voidConfigTypes.ts b/src/vs/platform/void/common/voidConfigTypes.ts new file mode 100644 index 000000000..cdd3e18fc --- /dev/null +++ b/src/vs/platform/void/common/voidConfigTypes.ts @@ -0,0 +1,232 @@ + +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Glass Devtools, Inc. All rights reserved. + * Void Editor additions licensed under the AGPL 3.0 License. + *--------------------------------------------------------------------------------------------*/ + +import { defaultAnthropicModels, defaultGeminiModels, defaultGroqModels, defaultOpenAIModels } from './voidConfigModelDefaults.js' + + + +export const voidProviderDefaults = { + anthropic: { + apiKey: '', + }, + openAI: { + apiKey: '', + }, + ollama: { + endpoint: 'http://127.0.0.1:11434', + }, + openRouter: { + apiKey: '', + }, + openAICompatible: { + apiKey: '', + endpoint: '', + }, + gemini: { + apiKey: '', + }, + groq: { + apiKey: '' + } +} as const + + +export const voidInitModelOptions = { + anthropic: { + models: defaultAnthropicModels, + }, + openAI: { + models: defaultOpenAIModels, + }, + ollama: { + models: [], + }, + openRouter: { + models: [], // any string + }, + openAICompatible: { + models: [], + }, + gemini: { + models: defaultGeminiModels, + }, + groq: { + models: defaultGroqModels, + }, +} + + + +export type ProviderName = keyof typeof voidProviderDefaults +export const providerNames = Object.keys(voidProviderDefaults) as ProviderName[] + + + +// state +export type SettingsOfProvider = { + [providerName in ProviderName]: ( + { + [optionName in keyof typeof voidProviderDefaults[providerName]]: string + } + & + { + enabled: string, // 'true' | 'false' + maxTokens: string, + + models: string[], // if null, user can type in any string as a model + }) +} + + +type UnionOfKeys = T extends T ? keyof T : never; + +export type SettingName = UnionOfKeys + + + +type DisplayInfo = { + title: string, + type: string, + placeholder: string, +} + +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-key...' : // sk-ant-api03-key + providerName === 'openAI' ? 'sk-proj-key...' : + providerName === 'openRouter' ? 'sk-or-key...' : // sk-or-v1-key + providerName === 'gemini' ? 'key...' : + providerName === 'groq' ? 'gsk_key...' : + providerName === 'openAICompatible' ? 'sk-key...' : + '(never)', + } + } + else if (settingName === 'endpoint') { + return { + title: providerName === 'ollama' ? 'Your Ollama endpoint' : + providerName === 'openAICompatible' ? 'baseURL' // (do not include /chat/completions) + : '(never)', + type: 'string', + placeholder: providerName === 'ollama' ? voidProviderDefaults.ollama.endpoint + : providerName === 'openAICompatible' ? 'https://my-website.com/v1' + : '(never)', + } + } + else if (settingName === 'maxTokens') { + return { + title: 'Max Tokens', + type: 'number', + placeholder: '1024', + } + } + else if (settingName === 'enabled') { + return { + title: 'Enabled?', + type: 'boolean', + placeholder: '(never)', + } + } + else if (settingName === 'models') { + return { + title: 'Available Models', + type: '(never)', + placeholder: '(never)', + } + } + + throw new Error(`displayInfo: Unknown setting name: "${settingName}"`) + +} + + +// used when waiting and for a type reference +export const defaultVoidProviderState: SettingsOfProvider = { + anthropic: { + ...voidProviderDefaults.anthropic, + ...voidInitModelOptions.anthropic, + enabled: 'false', + maxTokens: '', + }, + openAI: { + ...voidProviderDefaults.openAI, + ...voidInitModelOptions.openAI, + enabled: 'false', + maxTokens: '', + }, + ollama: { + ...voidProviderDefaults.ollama, + ...voidInitModelOptions.ollama, + enabled: 'false', + maxTokens: '', + }, + openRouter: { + ...voidProviderDefaults.openRouter, + ...voidInitModelOptions.openRouter, + enabled: 'false', + maxTokens: '', + }, + openAICompatible: { + ...voidProviderDefaults.openAICompatible, + ...voidInitModelOptions.openAICompatible, + enabled: 'false', + maxTokens: '', + }, + gemini: { + ...voidProviderDefaults.gemini, + ...voidInitModelOptions.gemini, + enabled: 'false', + maxTokens: '', + }, + groq: { + ...voidProviderDefaults.groq, + ...voidInitModelOptions.groq, + enabled: 'false', + maxTokens: '', + } +} + + + +// this is a state +export type ModelSelectionOfFeature = { + 'Ctrl+L': { + providerName: ProviderName, + modelName: string, + } | null, + 'Ctrl+K': { + providerName: ProviderName, + modelName: string, + } | null, + 'Autocomplete': { + providerName: ProviderName, + modelName: string, + } | null, +} +export type FeatureName = keyof ModelSelectionOfFeature +export const featureNames = ['Ctrl+L', 'Ctrl+K', 'Autocomplete'] as const + diff --git a/src/vs/workbench/contrib/void/browser/react/src/sendLLMMessage/anthropic.tsx b/src/vs/platform/void/electron-main/llmMessage/anthropic.ts similarity index 50% rename from src/vs/workbench/contrib/void/browser/react/src/sendLLMMessage/anthropic.tsx rename to src/vs/platform/void/electron-main/llmMessage/anthropic.ts index 36fa78acb..862822823 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sendLLMMessage/anthropic.tsx +++ b/src/vs/platform/void/electron-main/llmMessage/anthropic.ts @@ -1,17 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { SendLLMMessageFnTypeInternal } from './_types.js'; -import { parseMaxTokensStr } from '../../../registerConfig.js'; +import { parseMaxTokensStr } from './util.js'; +import { _InternalSendLLMMessageFnType } 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: _InternalSendLLMMessageFnType = ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter }) => { + + const thisConfig = settingsOfProvider.anthropic - const thisConfig = voidConfig.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 }); // defaults to process.env["ANTHROPIC_API_KEY"] + const anthropic = new Anthropic({ apiKey: thisConfig.apiKey, dangerouslyAllowBrowser: true }); // find system messages and concatenate them const systemMessage = messages @@ -22,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(voidConfig.default.maxTokens)!, // this might be undefined, but it will just throw an error for the user + model: modelName, + max_tokens: maxTokens, }); @@ -45,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/workbench/contrib/void/browser/react/src/sendLLMMessage/gemini.tsx b/src/vs/platform/void/electron-main/llmMessage/gemini.ts similarity index 55% rename from src/vs/workbench/contrib/void/browser/react/src/sendLLMMessage/gemini.tsx rename to src/vs/platform/void/electron-main/llmMessage/gemini.ts index 5a883177e..59e0c1c36 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sendLLMMessage/gemini.tsx +++ 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 './_types.js'; +import { _InternalSendLLMMessageFnType } from '../../common/llmMessageTypes.js'; // Gemini -export const sendGeminiMsg: SendLLMMessageFnTypeInternal = async ({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter }) => { +export const sendGeminiMsg: _InternalSendLLMMessageFnType = 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 genAI = new GoogleGenerativeAI(thisConfig.apiKey); + 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 new file mode 100644 index 000000000..21ac3f711 --- /dev/null +++ b/src/vs/platform/void/electron-main/llmMessage/greptile.ts @@ -0,0 +1,68 @@ +/*--------------------------------------------------------------------------------------------- + * 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 '../../common/llmMessageTypes.js'; + +// export const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, settingsOfProvider, _setAborter }) => { + +// let fullText = '' + +// const thisConfig = settingsOfProvider.greptile + +// fetch('https://api.greptile.com/v2/query', { +// method: 'POST', +// headers: { +// 'Authorization': `Bearer ${thisConfig.apikey}`, +// 'X-Github-Token': `${thisConfig.githubPAT}`, +// 'Content-Type': `application/json`, +// }, +// body: JSON.stringify({ +// messages, +// stream: true, +// repositories: [thisConfig.repoinfo], +// }), +// }) +// // this is {message}\n{message}\n{message}...\n +// .then(async response => { +// const text = await response.text() +// console.log('got greptile', text) +// return JSON.parse(`[${text.trim().split('\n').join(',')}]`) +// }) +// // TODO make this actually stream, right now it just sends one message at the end +// // TODO add _setAborter() when add streaming +// .then(async responseArr => { + +// for (const response of responseArr) { +// const type: string = response['type'] +// const message = response['message'] + +// // when receive text +// if (type === 'message') { +// fullText += message +// onText({ newText: message, fullText }) +// } +// else if (type === 'sources') { +// const { filepath, linestart: _, lineend: _2 } = message as { filepath: string; linestart: number | null; lineend: number | null } +// fullText += filepath +// onText({ newText: filepath, fullText }) +// } +// // type: 'status' with an empty 'message' means last message +// else if (type === 'status') { +// if (!message) { +// onFinalMessage({ fullText }) +// } +// } +// } + +// }) +// .catch(error => { +// onError({ error }) +// }); + +// } diff --git a/src/vs/platform/void/electron-main/llmMessage/groq.ts b/src/vs/platform/void/electron-main/llmMessage/groq.ts new file mode 100644 index 000000000..5ab592112 --- /dev/null +++ b/src/vs/platform/void/electron-main/llmMessage/groq.ts @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Glass Devtools, Inc. All rights reserved. + * Void Editor additions licensed under the AGPL 3.0 License. + *--------------------------------------------------------------------------------------------*/ + +import Groq from 'groq-sdk'; +import { _InternalSendLLMMessageFnType } from '../../common/llmMessageTypes.js'; +import { parseMaxTokensStr } from './util.js'; + +// Groq +export const sendGroqMsg: _InternalSendLLMMessageFnType = async ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter }) => { + let fullText = ''; + + const thisConfig = settingsOfProvider.groq + + const groq = new Groq({ + apiKey: thisConfig.apiKey, + dangerouslyAllowBrowser: true + }); + + await groq.chat.completions + .create({ + messages: messages, + model: modelName, + stream: true, + temperature: 0.7, + max_tokens: parseMaxTokensStr(thisConfig.maxTokens), + }) + .then(async response => { + _setAborter(() => response.controller.abort()) + // when receive text + for await (const chunk of response) { + const newText = chunk.choices[0]?.delta?.content || ''; + if (newText) { + fullText += newText; + onText({ newText, fullText }); + } + } + + onFinalMessage({ fullText }); + }) + .catch(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 new file mode 100644 index 000000000..ba04e1177 --- /dev/null +++ b/src/vs/platform/void/electron-main/llmMessage/ollama.ts @@ -0,0 +1,82 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Glass Devtools, Inc. All rights reserved. + * Void Editor additions licensed under the AGPL 3.0 License. + *--------------------------------------------------------------------------------------------*/ + +import { Ollama } from 'ollama'; +import { _InternalOllamaListFnType, _InternalSendLLMMessageFnType, ModelResponse } from '../../common/llmMessageTypes.js'; +import { parseMaxTokensStr } from './util.js'; + +export const ollamaList: _InternalOllamaListFnType = async ({ onSuccess: onSuccess_, onError: onError_, settingsOfProvider }) => { + + const onSuccess = ({ models }: { models: ModelResponse[] }) => { + onSuccess_({ models }) + } + + const onError = ({ error }: { error: string }) => { + onError_({ error }) + } + + + try { + const thisConfig = settingsOfProvider.ollama + const ollama = new Ollama({ host: thisConfig.endpoint }) + ollama.list() + .then((response) => { + const { models } = response + onSuccess({ models }) + }) + .catch((error) => { + onError({ error: error + '' }) + }) + } + catch (error) { + onError({ error: error + '' }) + } +} + + +// Ollama +export const sendOllamaMsg: _InternalSendLLMMessageFnType = ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter }) => { + + const thisConfig = settingsOfProvider.ollama + + let fullText = '' + + const ollama = new Ollama({ host: thisConfig.endpoint }) + + ollama.chat({ + model: modelName, + messages: messages, + stream: true, + options: { num_predict: parseMaxTokensStr(thisConfig.maxTokens) } // this is max_tokens + }) + .then(async stream => { + _setAborter(() => stream.abort()) + // iterate through the stream + for await (const chunk of stream) { + const newText = chunk.message.content; + fullText += newText; + onText({ newText, fullText }); + } + onFinalMessage({ fullText }); + + }) + // 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({ message: error + '', fullError: error }) + }) + +}; + + + +// ['codestral', 'qwen2.5-coder', 'qwen2.5-coder:0.5b', 'qwen2.5-coder:1.5b', 'qwen2.5-coder:3b', 'qwen2.5-coder:7b', 'qwen2.5-coder:14b', 'qwen2.5-coder:32b', 'codegemma', 'codegemma:2b', 'codegemma:7b', 'codellama', 'codellama:7b', 'codellama:13b', 'codellama:34b', 'codellama:70b', 'codellama:code', 'codellama:python', 'command-r', 'command-r:35b', 'command-r-plus', 'command-r-plus:104b', 'deepseek-coder-v2', 'deepseek-coder-v2:16b', 'deepseek-coder-v2:236b', 'falcon2', 'falcon2:11b', 'firefunction-v2', 'firefunction-v2:70b', 'gemma', 'gemma:2b', 'gemma:7b', 'gemma2', 'gemma2:2b', 'gemma2:9b', 'gemma2:27b', 'llama2', 'llama2:7b', 'llama2:13b', 'llama2:70b', 'llama3', 'llama3:8b', 'llama3:70b', 'llama3-chatqa', 'llama3-chatqa:8b', 'llama3-chatqa:70b', 'llama3-gradient', 'llama3-gradient:8b', 'llama3-gradient:70b', 'llama3.1', 'llama3.1:8b', 'llama3.1:70b', 'llama3.1:405b', 'llava', 'llava:7b', 'llava:13b', 'llava:34b', 'llava-llama3', 'llava-llama3:8b', 'llava-phi3', 'llava-phi3:3.8b', 'mistral', 'mistral:7b', 'mistral-large', 'mistral-large:123b', 'mistral-nemo', 'mistral-nemo:12b', 'mixtral', 'mixtral:8x7b', 'mixtral:8x22b', 'moondream', 'moondream:1.8b', 'openhermes', 'openhermes:v2.5', 'phi3', 'phi3:3.8b', 'phi3:14b', 'phi3.5', 'phi3.5:3.8b', 'qwen', 'qwen:7b', 'qwen:14b', 'qwen:32b', 'qwen:72b', 'qwen:110b', 'qwen2', 'qwen2:0.5b', 'qwen2:1.5b', 'qwen2:7b', 'qwen2:72b', 'smollm', 'smollm:135m', 'smollm:360m', 'smollm:1.7b',] diff --git a/src/vs/platform/void/electron-main/llmMessage/openai.ts b/src/vs/platform/void/electron-main/llmMessage/openai.ts new file mode 100644 index 000000000..3b8c36450 --- /dev/null +++ b/src/vs/platform/void/electron-main/llmMessage/openai.ts @@ -0,0 +1,68 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Glass Devtools, Inc. All rights reserved. + * Void Editor additions licensed under the AGPL 3.0 License. + *--------------------------------------------------------------------------------------------*/ + +import OpenAI from 'openai'; +import { _InternalSendLLMMessageFnType } from '../../common/llmMessageTypes.js'; +import { parseMaxTokensStr } from './util.js'; + + +// OpenAI, OpenRouter, OpenAICompatible +export const sendOpenAIMsg: _InternalSendLLMMessageFnType = ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName }) => { + + let fullText = '' + + let openai: OpenAI + let options: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming + + + if (providerName === 'openAI') { + const thisConfig = settingsOfProvider.openAI + openai = new OpenAI({ apiKey: thisConfig.apiKey, dangerouslyAllowBrowser: true }); + options = { model: modelName, messages: messages, stream: true, max_completion_tokens: parseMaxTokensStr(thisConfig.maxTokens) } + } + else if (providerName === 'openRouter') { + const thisConfig = settingsOfProvider.openRouter + openai = new OpenAI({ + baseURL: 'https://openrouter.ai/api/v1', apiKey: thisConfig.apiKey, dangerouslyAllowBrowser: true, + defaultHeaders: { + 'HTTP-Referer': 'https://voideditor.com', // Optional, for including your app on openrouter.ai rankings. + 'X-Title': 'Void Editor', // Optional. Shows in rankings on openrouter.ai. + }, + }); + options = { model: modelName, messages: messages, stream: true, max_completion_tokens: parseMaxTokensStr(thisConfig.maxTokens) } + } + else if (providerName === 'openAICompatible') { + const thisConfig = settingsOfProvider.openAICompatible + openai = new OpenAI({ baseURL: thisConfig.endpoint, apiKey: thisConfig.apiKey, dangerouslyAllowBrowser: true }) + options = { model: modelName, messages: messages, stream: true, max_completion_tokens: parseMaxTokensStr(thisConfig.maxTokens) } + } + else { + console.error(`sendOpenAIMsg: invalid providerName: ${providerName}`) + throw new Error(`providerName was invalid: ${providerName}`) + } + + openai.chat.completions + .create(options) + .then(async response => { + _setAborter(() => response.controller.abort()) + // when receive text + for await (const chunk of response) { + const newText = chunk.choices[0]?.delta?.content || ''; + fullText += newText; + onText({ newText, fullText }); + } + onFinalMessage({ fullText }); + }) + // when error/fail - this catches errors of both .create() and .then(for await) + .catch(error => { + if (error instanceof OpenAI.APIError && error.status === 401) { + onError({ message: 'Invalid API key.', fullError: error }); + } + else { + onError({ message: error, fullError: error }); + } + }) + +}; diff --git a/src/vs/workbench/contrib/void/browser/react/src/sendLLMMessage/sendLLMMessage.tsx b/src/vs/platform/void/electron-main/llmMessage/sendLLMMessage.ts similarity index 53% rename from src/vs/workbench/contrib/void/browser/react/src/sendLLMMessage/sendLLMMessage.tsx rename to src/vs/platform/void/electron-main/llmMessage/sendLLMMessage.ts index f5bca7dd4..9e106f97a 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sendLLMMessage/sendLLMMessage.tsx +++ b/src/vs/platform/void/electron-main/llmMessage/sendLLMMessage.ts @@ -1,23 +1,39 @@ -import { posthog } from 'posthog-js' -import type { OnText, OnError, OnFinalMessage, SendLLMMMessageParams, } from '../../../../../../../platform/void/common/llmMessageTypes.js'; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Glass Devtools, Inc. All rights reserved. + * Void Editor additions licensed under the AGPL 3.0 License. + *--------------------------------------------------------------------------------------------*/ + +import { LLMMMessageParams, OnText, OnFinalMessage, OnError } from '../../common/llmMessageTypes.js'; +import { IMetricsService } from '../../common/metricsService.js'; + import { sendAnthropicMsg } from './anthropic.js'; -import { sendGeminiMsg } from './gemini.js'; -import { sendGreptileMsg } from './greptile.js'; -import { sendGroqMsg } from './groq.js'; import { sendOllamaMsg } from './ollama.js'; import { sendOpenAIMsg } from './openai.js'; +import { sendGeminiMsg } from './gemini.js'; +import { sendGroqMsg } from './groq.js'; - -export const sendLLMMessage = ({ messages, onText: onText_, onFinalMessage: onFinalMessage_, onError: onError_, abortRef: abortRef_, voidConfig, logging: { loggingName } }: SendLLMMMessageParams) => { - if (!voidConfig) return; +export const sendLLMMessage = ({ + messages, + onText: onText_, + onFinalMessage: onFinalMessage_, + onError: onError_, + abortRef: abortRef_, + logging: { loggingName }, + settingsOfProvider, + providerName, + modelName, +}: LLMMMessageParams, + + metricsService: IMetricsService +) => { // trim message content (Anthropic and other providers give an error if there is trailing whitespace) messages = messages.map(m => ({ ...m, content: m.content.trim() })) // only captures number of messages and message "shape", no actual code, instructions, prompts, etc const captureChatEvent = (eventId: string, extras?: object) => { - posthog.capture(eventId, { - whichApi: voidConfig.default['whichApi'], + metricsService.capture(eventId, { + providerName, numMessages: messages?.length, messagesShape: messages?.map(msg => ({ role: msg.role, length: msg.content.length })), version: '2024-11-14', @@ -43,16 +59,18 @@ export const sendLLMMessage = ({ messages, onText: onText_, onFinalMessage: onFi onFinalMessage_({ fullText }) } - const onError: OnError = ({ error }) => { - console.error('sendLLMMessage 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 = () => { captureChatEvent(`${loggingName} - Abort`, { messageLengthSoFar: _fullTextSoFar.length }) - _aborter?.() + try { _aborter?.() } // aborter sometimes automatically throws an error + catch (e) { } _didAbort = true } abortRef_.current = onAbort @@ -60,38 +78,35 @@ export const sendLLMMessage = ({ messages, onText: onText_, onFinalMessage: onFi captureChatEvent(`${loggingName} - Sending Message`, { messageLength: messages[messages.length - 1]?.content.length }) try { - switch (voidConfig.default.whichApi) { + switch (providerName) { case 'anthropic': - sendAnthropicMsg({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter, }); + sendAnthropicMsg({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName }); break; case 'openAI': case 'openRouter': case 'openAICompatible': - sendOpenAIMsg({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter, }); + sendOpenAIMsg({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName }); break; case 'gemini': - sendGeminiMsg({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter, }); + sendGeminiMsg({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName }); break; case 'ollama': - sendOllamaMsg({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter, }); - break; - case 'greptile': - sendGreptileMsg({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter, }); + sendOllamaMsg({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName }); break; case 'groq': - sendGroqMsg({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter, }); + sendGroqMsg({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName }); break; default: - onError({ error: `Error: whichApi was "${voidConfig.default.whichApi}", 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}` }); } - ; (_aborter as any)?.() - _didAbort = true + 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 new file mode 100644 index 000000000..988e47067 --- /dev/null +++ b/src/vs/platform/void/electron-main/llmMessage/util.ts @@ -0,0 +1,14 @@ +/*--------------------------------------------------------------------------------------------- + * 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 + const int = isNaN(Number(maxTokensStr)) ? undefined : parseInt(maxTokensStr) + if (Number.isNaN(int)) + return undefined + return int +} + + diff --git a/src/vs/platform/void/electron-main/llmMessageChannel.ts b/src/vs/platform/void/electron-main/llmMessageChannel.ts index dd4372866..803539d94 100644 --- a/src/vs/platform/void/electron-main/llmMessageChannel.ts +++ b/src/vs/platform/void/electron-main/llmMessageChannel.ts @@ -1,46 +1,54 @@ /*--------------------------------------------------------------------------------------------- * 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. *--------------------------------------------------------------------------------------------*/ -// this channel is registered in `app.ts` -// code convention is to make a service responsible for this stuff, and not a channel, but this is simpler. -// you could create one instance in electron-main/my-service.ts and one in browser/my-service.ts (and define the interface IMyService in common/my-service.ts), but we just use a channel here -// registerSingleton(ISendLLMMessageService, SendLLMMessageService, InstantiationType.Delayed); +// registered in app.ts +// code convention is to make a service responsible for this stuff, and not a channel, but having fewer files is simpler... import { IServerChannel } from '../../../base/parts/ipc/common/ipc.js'; import { Emitter, Event } from '../../../base/common/event.js'; -import { sendLLMMessage } from '../../../workbench/contrib/void/browser/react/out/sendLLMMessage/sendLLMMessage.js'; -import { listenerNames, ProxyOnTextPayload, ProxyOnErrorPayload, ProxyOnFinalMessagePayload, ProxyLLMMessageParams, AbortRef, SendLLMMMessageParams, ProxyLLMMessageAbortParams } from '../common/llmMessageTypes.js'; +import { EventLLMMessageOnTextParams, EventLLMMessageOnErrorParams, EventLLMMessageOnFinalMessageParams, MainLLMMessageParams, AbortRef, LLMMMessageParams, MainLLMMessageAbortParams, MainOllamaListParams, OllamaListParams, EventOllamaListOnSuccessParams, EventOllamaListOnErrorParams } from '../common/llmMessageTypes.js'; +import { sendLLMMessage } from './llmMessage/sendLLMMessage.js' +import { IMetricsService } from '../common/metricsService.js'; +import { ollamaList } from './llmMessage/ollama.js'; -// NODE IMPLEMENTATION OF SENDLLMMESSAGE - calls sendLLMMessage() and returns listeners +// NODE IMPLEMENTATION - calls actual sendLLMMessage() and returns listeners to it export class LLMMessageChannel implements IServerChannel { - private readonly _onText = new Emitter(); - readonly onText = this._onText.event; + // sendLLMMessage + private readonly _onText_llm = new Emitter(); + private readonly _onFinalMessage_llm = new Emitter(); + private readonly _onError_llm = new Emitter(); - private readonly _onFinalMessage = new Emitter(); - readonly onFinalMessage = this._onFinalMessage.event; + // abort + private readonly _abortRefOfRequestId_llm: Record = {} - private readonly _onError = new Emitter(); - readonly onError = this._onError.event; + // ollamaList + private readonly _onSuccess_ollama = new Emitter(); + private readonly _onError_ollama = new Emitter(); - - private readonly _abortRefOfRequestId: Record = {} - - - constructor() { } + // stupidly, channels can't take in @IService + constructor( + private readonly metricsService: IMetricsService, + ) { } // browser uses this to listen for changes - listen(_: unknown, event: typeof listenerNames[number]): Event { - if (event === 'onText') { - return this.onText; + listen(_: unknown, event: string): Event { + if (event === 'onText_llm') { + return this._onText_llm.event; + } + else if (event === 'onFinalMessage_llm') { + return this._onFinalMessage_llm.event; } - else if (event === 'onFinalMessage') { - return this.onFinalMessage; + else if (event === 'onError_llm') { + return this._onError_llm.event; } - else if (event === 'onError') { - return this.onError; + else if (event === 'onSuccess_ollama') { + return this._onSuccess_ollama.event; + } + else if (event === 'onError_ollama') { + return this._onError_ollama.event; } else { throw new Error(`Event not found: ${event}`); @@ -49,7 +57,6 @@ export class LLMMessageChannel implements IServerChannel { // browser uses this to call async call(_: unknown, command: string, params: any): Promise { - try { if (command === 'sendLLMMessage') { this._callSendLLMMessage(params) @@ -57,6 +64,9 @@ export class LLMMessageChannel implements IServerChannel { else if (command === 'abort') { this._callAbort(params) } + else if (command === 'ollamaList') { + this._callOllamaList(params) + } else { throw new Error(`Void sendLLM: command "${command}" not recognized.`) } @@ -67,27 +77,38 @@ export class LLMMessageChannel implements IServerChannel { } // the only place sendLLMMessage is actually called - private _callSendLLMMessage(params: ProxyLLMMessageParams) { + private async _callSendLLMMessage(params: MainLLMMessageParams) { const { requestId } = params; - if (!(requestId in this._abortRefOfRequestId)) - this._abortRefOfRequestId[requestId] = { current: null } + if (!(requestId in this._abortRefOfRequestId_llm)) + this._abortRefOfRequestId_llm[requestId] = { current: null } - const mainThreadParams: SendLLMMMessageParams = { + const mainThreadParams: LLMMMessageParams = { ...params, - onText: ({ newText, fullText }) => { this._onText.fire({ requestId, newText, fullText }); }, - onFinalMessage: ({ fullText }) => { this._onFinalMessage.fire({ requestId, fullText }); }, - onError: ({ error }) => { this._onError.fire({ requestId, error }); }, - abortRef: this._abortRefOfRequestId[requestId], + onText: ({ newText, fullText }) => { this._onText_llm.fire({ requestId, newText, fullText }); }, + onFinalMessage: ({ fullText }) => { this._onFinalMessage_llm.fire({ requestId, fullText }); }, + onError: ({ message: error, fullError }) => { console.log('sendLLM: firing err'); this._onError_llm.fire({ requestId, message: error, fullError }); }, + abortRef: this._abortRefOfRequestId_llm[requestId], } - sendLLMMessage(mainThreadParams); + sendLLMMessage(mainThreadParams, this.metricsService); } - private _callAbort(params: ProxyLLMMessageAbortParams) { + private _callAbort(params: MainLLMMessageAbortParams) { const { requestId } = params; - if (!(requestId in this._abortRefOfRequestId)) return - this._abortRefOfRequestId[requestId].current?.() - delete this._abortRefOfRequestId[requestId] + if (!(requestId in this._abortRefOfRequestId_llm)) return + this._abortRefOfRequestId_llm[requestId].current?.() + delete this._abortRefOfRequestId_llm[requestId] + } + + private _callOllamaList(params: MainOllamaListParams) { + const { requestId } = params; + + const mainThreadParams: OllamaListParams = { + ...params, + onSuccess: ({ models }) => { this._onSuccess_ollama.fire({ requestId, models }); }, + onError: ({ error }) => { this._onError_ollama.fire({ requestId, error }); }, + } + ollamaList(mainThreadParams) } diff --git a/src/vs/platform/void/electron-main/metricsMainService.ts b/src/vs/platform/void/electron-main/metricsMainService.ts new file mode 100644 index 000000000..aaaf8119d --- /dev/null +++ b/src/vs/platform/void/electron-main/metricsMainService.ts @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Glass Devtools, Inc. All rights reserved. + * Void Editor additions licensed under the AGPL 3.0 License. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from '../../../base/common/lifecycle.js'; +import { ITelemetryService } from '../../telemetry/common/telemetry.js'; + +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'; + +export class MetricsMainService extends Disposable implements IMetricsService { + _serviceBrand: undefined; + + readonly _distinctId: string + readonly client: PostHog + + constructor( + @ITelemetryService private readonly _telemetryService: ITelemetryService + ) { + super() + this.client = new PostHog('phc_UanIdujHiLp55BkUTjB1AuBXcasVkdqRwgnwRlWESH2', { host: 'https://us.i.posthog.com', }) + + const { devDeviceId, firstSessionDate, machineId } = this._telemetryService + this._distinctId = devDeviceId + this.client.identify({ distinctId: devDeviceId, properties: { firstSessionDate, machineId } }) + + console.log('Void posthog metrics info:', JSON.stringify({ devDeviceId, firstSessionDate, machineId })) + } + + capture: IMetricsService['capture'] = (event, 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 0b426a160..64925e46b 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/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 5d7b93cfd..c504eae08 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -239,7 +239,7 @@ MenuRegistry.appendMenuItems([{ group: '3_workbench_layout_move', command: { id: ToggleSidebarPositionAction.ID, - title: localize('move second sidebar left', "Move Secondary Side Bar Left") + title: localize('move second sidebar left', "Move Void Side Bar Left") }, when: ContextKeyExpr.and(ContextKeyExpr.notEquals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.AuxiliaryBar))), order: 1 @@ -250,7 +250,7 @@ MenuRegistry.appendMenuItems([{ group: '3_workbench_layout_move', command: { id: ToggleSidebarPositionAction.ID, - title: localize('move second sidebar right', "Move Secondary Side Bar Right") + title: localize('move second sidebar right', "Move Void Side Bar Right") }, when: ContextKeyExpr.and(ContextKeyExpr.equals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.AuxiliaryBar))), order: 1 @@ -949,7 +949,7 @@ registerAction2(class extends Action2 { if (!hasAddedView) { results.push({ type: 'separator', - label: localize('secondarySideBarContainer', "Secondary Side Bar / {0}", containerModel.title) + label: localize('secondarySideBarContainer', "Void Side Bar / {0}", containerModel.title) }); hasAddedView = true; } @@ -1056,7 +1056,7 @@ class MoveFocusedViewAction extends Action2 { if (!(isViewSolo && currentLocation === ViewContainerLocation.AuxiliaryBar)) { items.push({ id: '_.auxiliarybar.newcontainer', - label: localize('moveFocusedView.newContainerInSidePanel', "New Secondary Side Bar Entry") + label: localize('moveFocusedView.newContainerInSidePanel', "New Void Side Bar Entry") }); } @@ -1104,7 +1104,7 @@ class MoveFocusedViewAction extends Action2 { items.push({ type: 'separator', - label: localize('secondarySideBar', "Secondary Side Bar") + label: localize('secondarySideBar', "Void Side Bar") }); const pinnedAuxPanels = paneCompositePartService.getPinnedPaneCompositeIds(ViewContainerLocation.AuxiliaryBar); @@ -1386,7 +1386,7 @@ if (!isMacintosh || !isNative) { ToggleVisibilityActions.push(...[ CreateToggleLayoutItem(ToggleActivityBarVisibilityActionId, ContextKeyExpr.notEquals('config.workbench.activityBar.location', 'hidden'), localize('activityBar', "Activity Bar"), { whenA: ContextKeyExpr.equals('config.workbench.sideBar.location', 'left'), iconA: activityBarLeftIcon, iconB: activityBarRightIcon }), CreateToggleLayoutItem(ToggleSidebarVisibilityAction.ID, SideBarVisibleContext, localize('sideBar', "Primary Side Bar"), { whenA: ContextKeyExpr.equals('config.workbench.sideBar.location', 'left'), iconA: panelLeftIcon, iconB: panelRightIcon }), - CreateToggleLayoutItem(ToggleAuxiliaryBarAction.ID, AuxiliaryBarVisibleContext, localize('secondarySideBar', "Secondary Side Bar"), { whenA: ContextKeyExpr.equals('config.workbench.sideBar.location', 'left'), iconA: panelRightIcon, iconB: panelLeftIcon }), + CreateToggleLayoutItem(ToggleAuxiliaryBarAction.ID, AuxiliaryBarVisibleContext, localize('secondarySideBar', "Void Side Bar"), { whenA: ContextKeyExpr.equals('config.workbench.sideBar.location', 'left'), iconA: panelRightIcon, iconB: panelLeftIcon }), CreateToggleLayoutItem(TogglePanelAction.ID, PanelVisibleContext, localize('panel', "Panel"), panelIcon), CreateToggleLayoutItem(ToggleStatusbarVisibilityAction.ID, ContextKeyExpr.equals('config.workbench.statusBar.visible', true), localize('statusBar', "Status Bar"), statusBarIcon), ]); diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 7290a2f70..e39d7ce69 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -2434,7 +2434,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi comment: 'Information about the layout of the workbench during statup'; activityBarVisible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether or the not the activity bar is visible' }; sideBarVisible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether or the not the primary side bar is visible' }; - auxiliaryBarVisible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether or the not the secondary side bar is visible' }; + auxiliaryBarVisible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether or the not the Void side bar is visible' }; panelVisible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether or the not the panel is visible' }; statusbarVisible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether or the not the status bar is visible' }; sideBarPosition: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the primary side bar is on the left or right' }; diff --git a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts index b2c7e5f01..93b4ae6df 100644 --- a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts +++ b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts @@ -26,7 +26,7 @@ const auxiliaryBarLeftOffIcon = registerIcon('auxiliarybar-left-off-layout-icon' export class ToggleAuxiliaryBarAction extends Action2 { static readonly ID = 'workbench.action.toggleAuxiliaryBar'; - static readonly LABEL = localize2('toggleAuxiliaryBar', "Toggle Secondary Side Bar Visibility"); + static readonly LABEL = localize2('toggleAuxiliaryBar', "Toggle Void Side Bar Visibility"); constructor() { super({ @@ -34,7 +34,7 @@ export class ToggleAuxiliaryBarAction extends Action2 { title: ToggleAuxiliaryBarAction.LABEL, toggled: { condition: AuxiliaryBarVisibleContext, - title: localize('secondary sidebar', "Secondary Side Bar"), + title: localize('secondary sidebar', "Void Side Bar"), mnemonicTitle: localize({ key: 'secondary sidebar mnemonic', comment: ['&& denotes a mnemonic'] }, "Secondary Si&&de Bar"), }, @@ -70,7 +70,7 @@ registerAction2(ToggleAuxiliaryBarAction); registerAction2(class FocusAuxiliaryBarAction extends Action2 { static readonly ID = 'workbench.action.focusAuxiliaryBar'; - static readonly LABEL = localize2('focusAuxiliaryBar', "Focus into Secondary Side Bar"); + static readonly LABEL = localize2('focusAuxiliaryBar', "Focus into Void Side Bar"); constructor() { super({ @@ -103,7 +103,7 @@ MenuRegistry.appendMenuItems([ group: '0_workbench_toggles', command: { id: ToggleAuxiliaryBarAction.ID, - title: localize('toggleSecondarySideBar', "Toggle Secondary Side Bar"), + title: localize('toggleSecondarySideBar', "Toggle Void Side Bar"), toggled: { condition: AuxiliaryBarVisibleContext, icon: auxiliaryBarLeftIcon }, icon: auxiliaryBarLeftOffIcon, }, @@ -116,7 +116,7 @@ MenuRegistry.appendMenuItems([ group: '0_workbench_toggles', command: { id: ToggleAuxiliaryBarAction.ID, - title: localize('toggleSecondarySideBar', "Toggle Secondary Side Bar"), + title: localize('toggleSecondarySideBar', "Toggle Void Side Bar"), toggled: { condition: AuxiliaryBarVisibleContext, icon: auxiliaryBarRightIcon }, icon: auxiliaryBarRightOffIcon, }, @@ -129,7 +129,7 @@ MenuRegistry.appendMenuItems([ group: '3_workbench_layout_move', command: { id: ToggleAuxiliaryBarAction.ID, - title: localize2('hideAuxiliaryBar', 'Hide Secondary Side Bar'), + title: localize2('hideAuxiliaryBar', 'Hide Void Side Bar'), }, when: ContextKeyExpr.and(AuxiliaryBarVisibleContext, ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.AuxiliaryBar))), order: 2 diff --git a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts index 264794c15..f3718162e 100644 --- a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts +++ b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts @@ -195,11 +195,13 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart { const positionActions: IAction[] = []; createAndFillInContextMenuActions(activityBarPositionMenu, { primary: [], secondary: positionActions }); + + // appears when right click actions.push(...[ new Separator(), new SubmenuAction('workbench.action.panel.position', localize('activity bar position', "Activity Bar Position"), positionActions), - toAction({ id: ToggleSidebarPositionAction.ID, label: currentPositionRight ? localize('move second side bar left', "Move Secondary Side Bar Left") : localize('move second side bar right', "Move Secondary Side Bar Right"), run: () => this.commandService.executeCommand(ToggleSidebarPositionAction.ID) }), - toAction({ id: ToggleAuxiliaryBarAction.ID, label: localize('hide second side bar', "Hide Secondary Side Bar"), run: () => this.commandService.executeCommand(ToggleAuxiliaryBarAction.ID) }) + toAction({ id: ToggleSidebarPositionAction.ID, label: currentPositionRight ? localize('move second side bar left', "Move Void Side Bar Left") : localize('move second side bar right', "Move Void Side Bar Right"), run: () => this.commandService.executeCommand(ToggleSidebarPositionAction.ID) }), + toAction({ id: ToggleAuxiliaryBarAction.ID, label: localize('hide second side bar', "Hide Void Side Bar"), run: () => this.commandService.executeCommand(ToggleAuxiliaryBarAction.ID) }) ]); } diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index 77ac81eea..ac309d469 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - +// import './media/panelpart.css'; import { localize, localize2 } from '../../../../nls.js'; import { KeyMod, KeyCode } from '../../../../base/common/keyCodes.js'; @@ -342,7 +342,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.closeAuxiliaryBar', - title: localize2('closeSecondarySideBar', 'Hide Secondary Side Bar'), + title: localize2('closeSecondarySideBar', 'Hide Void Side Bar'), category: Categories.View, icon: closeIcon, menu: [{ @@ -415,14 +415,16 @@ class MoveViewsBetweenPanelsAction extends Action2 { } } -// --- Move Panel Views To Secondary Side Bar +// --- Move Panel Views To Void Side Bar + +// these are just for the command pallette class MovePanelToSidePanelAction extends MoveViewsBetweenPanelsAction { static readonly ID = 'workbench.action.movePanelToSidePanel'; constructor() { super(ViewContainerLocation.Panel, ViewContainerLocation.AuxiliaryBar, { id: MovePanelToSidePanelAction.ID, - title: localize2('movePanelToSecondarySideBar', "Move Panel Views To Secondary Side Bar"), + title: localize2('movePanelToSecondarySideBar', "Move Panel Views To Void Side Bar"), category: Categories.View, f1: false }); @@ -434,7 +436,7 @@ export class MovePanelToSecondarySideBarAction extends MoveViewsBetweenPanelsAct constructor() { super(ViewContainerLocation.Panel, ViewContainerLocation.AuxiliaryBar, { id: MovePanelToSecondarySideBarAction.ID, - title: localize2('movePanelToSecondarySideBar', "Move Panel Views To Secondary Side Bar"), + title: localize2('movePanelToSecondarySideBar', "Move Panel Views To Void Side Bar"), category: Categories.View, f1: true }); @@ -452,7 +454,7 @@ class MoveSidePanelToPanelAction extends MoveViewsBetweenPanelsAction { constructor() { super(ViewContainerLocation.AuxiliaryBar, ViewContainerLocation.Panel, { id: MoveSidePanelToPanelAction.ID, - title: localize2('moveSidePanelToPanel', "Move Secondary Side Bar Views To Panel"), + title: localize2('moveSidePanelToPanel', "Move Side Bar Views To Panel"), // Void - this seemed to have a typo before category: Categories.View, f1: false }); @@ -465,7 +467,7 @@ export class MoveSecondarySideBarToPanelAction extends MoveViewsBetweenPanelsAct constructor() { super(ViewContainerLocation.AuxiliaryBar, ViewContainerLocation.Panel, { id: MoveSecondarySideBarToPanelAction.ID, - title: localize2('moveSidePanelToPanel', "Move Secondary Side Bar Views To Panel"), + title: localize2('moveSidePanelToPanel', "Move Void Side Bar Views To Panel"), category: Categories.View, f1: true }); diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 4fffbf21c..a647578ba 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -512,7 +512,7 @@ const registry = Registry.as(ConfigurationExtensions.Con 'type': 'string', 'enum': ['left', 'right'], 'default': 'left', - 'description': localize('sideBarLocation', "Controls the location of the primary side bar and activity bar. They can either show on the left or right of the workbench. The secondary side bar will show on the opposite side of the workbench.") + 'description': localize('sideBarLocation', "Controls the location of the primary side bar and activity bar. They can either show on the left or right of the workbench. The Void side bar will show on the opposite side of the workbench.") }, 'workbench.panel.showLabel': { 'type': 'boolean', @@ -545,12 +545,12 @@ const registry = Registry.as(ConfigurationExtensions.Con 'type': 'string', 'enum': ['default', 'top', 'bottom', 'hidden'], 'default': 'default', - 'markdownDescription': localize({ comment: ['This is the description for a setting'], key: 'activityBarLocation' }, "Controls the location of the Activity Bar relative to the Primary and Secondary Side Bars."), + 'markdownDescription': localize({ comment: ['This is the description for a setting'], key: 'activityBarLocation' }, "Controls the location of the Activity Bar relative to the Primary and Void Side Bars."), 'enumDescriptions': [ - localize('workbench.activityBar.location.default', "Show the Activity Bar on the side of the Primary Side Bar and on top of the Secondary Side Bar."), - localize('workbench.activityBar.location.top', "Show the Activity Bar on top of the Primary and Secondary Side Bars."), - localize('workbench.activityBar.location.bottom', "Show the Activity Bar at the bottom of the Primary and Secondary Side Bars."), - localize('workbench.activityBar.location.hide', "Hide the Activity Bar in the Primary and Secondary Side Bars.") + localize('workbench.activityBar.location.default', "Show the Activity Bar on the side of the Primary Side Bar and on top of the Void Side Bar."), + localize('workbench.activityBar.location.top', "Show the Activity Bar on top of the Primary and Void Side Bars."), + localize('workbench.activityBar.location.bottom', "Show the Activity Bar at the bottom of the Primary and Void Side Bars."), + localize('workbench.activityBar.location.hide', "Hide the Activity Bar in the Primary and Void Side Bars.") ], }, 'workbench.activityBar.iconClickBehavior': { @@ -598,7 +598,7 @@ const registry = Registry.as(ConfigurationExtensions.Con 'description': localize('workbench.hover.delay', "Controls the delay in milliseconds after which the hover is shown for workbench items (ex. some extension provided tree view items). Already visible items may require a refresh before reflecting this setting change."), // Testing has indicated that on Windows and Linux 500 ms matches the native hovers most closely. // On Mac, the delay is 1500. - 'default': isMacintosh ? 1500 : 500, + 'default': isMacintosh ? 300 : 300, // <-- Void edited this to 300 : 300 (was 1500 : 500) 'minimum': 0 }, 'workbench.reduceMotion': { diff --git a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts index 164b2e982..b01a519ce 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts @@ -151,7 +151,7 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider { const paneComposites = this.paneCompositeService.getPaneComposites(location); diff --git a/src/vs/workbench/contrib/void/browser/findDiffs.ts b/src/vs/workbench/contrib/void/browser/findDiffs.ts index fac7f3db5..32b893e44 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 763441388..b1e9edf05 100644 --- a/src/vs/workbench/contrib/void/browser/getCmdKey.ts +++ b/src/vs/workbench/contrib/void/browser/getCmdKey.ts @@ -1,12 +1,14 @@ /*--------------------------------------------------------------------------------------------- * 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'; +import { isMacintosh } from '../../../../base/common/platform'; +// import { OperatingSystem, OS } from '../../../../base/common/platform.js'; +// OS === OperatingSystem.Macintosh export function getCmdKey(): string { - if (OS === OperatingSystem.Macintosh) { + if (isMacintosh) { return '⌘'; } else { return 'Ctrl'; diff --git a/src/vs/workbench/contrib/void/browser/media/void.css b/src/vs/workbench/contrib/void/browser/media/void.css index 90c274bab..cf3176807 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/stringifyFiles.ts b/src/vs/workbench/contrib/void/browser/prompt/stringifySelections.ts similarity index 51% rename from src/vs/workbench/contrib/void/browser/prompt/stringifyFiles.ts rename to src/vs/workbench/contrib/void/browser/prompt/stringifySelections.ts index 11056b433..17372ec9f 100644 --- a/src/vs/workbench/contrib/void/browser/prompt/stringifyFiles.ts +++ b/src/vs/workbench/contrib/void/browser/prompt/stringifySelections.ts @@ -1,13 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Glass Devtools, Inc. All rights reserved. + * Void Editor additions licensed under the AGPL 3.0 License. + *--------------------------------------------------------------------------------------------*/ import { CodeSelection } from '../registerThreads.js'; -export const filesStr = (selections: CodeSelection[]) => { +export const stringifySelections = (selections: CodeSelection[]) => { + + return selections.map(({ fileURI, content, selectionStr }) => `\ File: ${fileURI.fsPath} \`\`\` -${content} +${content // this was the enite file which is foolish + } \`\`\`${selectionStr === null ? '' : ` Selection: ${selectionStr}`} `).join('\n') @@ -17,7 +24,7 @@ Selection: ${selectionStr}`} export const userInstructionsStr = (instructions: string, selections: CodeSelection[] | null) => { let str = ''; if (selections && selections.length > 0) { - str += filesStr(selections); + str += stringifySelections(selections); str += `Please edit the selected code following these instructions:\n` } str += `${instructions}`; diff --git a/src/vs/workbench/contrib/void/browser/prompt/systemPrompts.ts b/src/vs/workbench/contrib/void/browser/prompt/systemPrompts.ts index 2faa8909a..157f4292d 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 481591da8..41e6e635a 100755 --- a/src/vs/workbench/contrib/void/browser/react/build.js +++ b/src/vs/workbench/contrib/void/browser/react/build.js @@ -1,10 +1,15 @@ +/*--------------------------------------------------------------------------------------------- + * 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 execSync('npx rimraf out/ && npx rimraf src2/') -// build and scope tailwind -execSync('npx scope-tailwind ./src -o src2/ -s void-scope -c styles.css -p "prefix-" ') +// build and scope tailwind: https://www.npmjs.com/package/scope-tailwind +execSync('npx scope-tailwind ./src -o src2/ -s void-scope -c styles.css -p "void-" ') // tsup to build src2/ into out/ execSync('npx tsup') 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 4773d645d..b2c83e661 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/MarkdownRender.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx similarity index 84% rename from src/vs/workbench/contrib/void/browser/react/src/markdown/MarkdownRender.tsx rename to src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx index e17beb37a..0977a18ad 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/MarkdownRender.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' @@ -16,6 +21,9 @@ const CodeButtonsOnHover = ({ diffRepr: text }: { diffRepr: string }) => { const [copyButtonState, setCopyButtonState] = useState(CopyButtonState.Copy) const inlineDiffService = useService('inlineDiffService') + const clipboardService = useService('clipboardService') + + useEffect(() => { if (copyButtonState !== CopyButtonState.Copy) { setTimeout(() => { @@ -25,15 +33,10 @@ const CodeButtonsOnHover = ({ diffRepr: text }: { diffRepr: string }) => { }, [copyButtonState]) const onCopy = useCallback(() => { - navigator.clipboard.writeText(text).then( - () => { - setCopyButtonState(CopyButtonState.Copied) - }, - () => { - setCopyButtonState(CopyButtonState.Error) - } - ) - }, [text]) + clipboardService.writeText(text) + .then(() => { setCopyButtonState(CopyButtonState.Copied) }) + .catch(() => { setCopyButtonState(CopyButtonState.Error) }) + }, [text, clipboardService]) return <> + )} + {showDismiss && onDismiss && ( + + )} + + + + {/* Expandable Details */} + {isExpanded && details && ( +
+
+ 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 000000000..c7f61fdfd --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/ModelSelectionSettings.tsx @@ -0,0 +1,94 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Glass Devtools, Inc. All rights reserved. + * Void Editor additions licensed under the AGPL 3.0 License. + *--------------------------------------------------------------------------------------------*/ + +import { useCallback, useEffect, useRef, useState } from 'react' +import { FeatureName, featureNames, ProviderName, providerNames } from '../../../../../../../platform/void/common/voidConfigTypes.js' +import { dummyModelData } from '../../../../../../../platform/void/common/voidConfigModelDefaults.js' +import { useConfigState, useRefreshModelState, useService } from '../util/services.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] }[] = [] + + for (const providerName of providerNames) { + const providerConfig = voidConfigState[providerName] + if (providerConfig.enabled !== 'true') continue + providerConfig.models?.forEach(model => { + modelOptions.push({ text: `${model} (${providerName})`, value: [providerName, model] }) + }) + } + + + const isDummy = modelOptions.length === 0 + if (isDummy) { + for (const [providerName, models] of Object.entries(dummyModelData)) { + for (let model of models) { + modelOptions.push({ text: `${model} (${providerName})`, value: ['dummy', 'dummy'] }) + } + } + } + + let weChangedText = false + + return <> +

{featureName}

+ { + { + if (isDummy) return // don't set state to the dummy value + if (weChangedText) return + + voidConfigService.setModelSelectionOfFeature(featureName, { providerName: newVal[0] as ProviderName, modelName: newVal[1] }) + }, [voidConfigService, featureName, isDummy])} + // we are responsible for setting the initial state here. always sync instance when state changes. + onCreateInstance={useCallback((instance: SelectBox) => { + const syncInstance = () => { + const settingsAtProvider = voidConfigService.state.modelSelectionOfFeature[featureName] + const index = modelOptions.findIndex(v => v.value[0] === settingsAtProvider?.providerName && v.value[1] === settingsAtProvider?.modelName) + if (index !== -1) { + weChangedText = true + instance.select(index) + weChangedText = false + } + } + syncInstance() + const disposable = voidConfigService.onDidChangeState(syncInstance) + return [disposable] + }, [voidConfigService, modelOptions, featureName])} + />} + + +} + +const RefreshModels = () => { + const refreshModelState = useRefreshModelState() + const refreshModelService = useService('refreshModelService') + + return <> + + {refreshModelState === 'loading' ? 'loading...' : '✅'} + +} + +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 e1786102e..d605e2d8d 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,11 +1,13 @@ /*--------------------------------------------------------------------------------------------- * 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' -import { SidebarSettings } from './SidebarSettings.js'; +// import { SidebarSettings } from './SidebarSettings.js'; + + import { useSidebarState } from '../util/services.js'; // import { SidebarThreadSelector } from './SidebarThreadSelector.js'; // import { SidebarChat } from './SidebarChat.js'; @@ -13,13 +15,17 @@ import { useSidebarState } from '../util/services.js'; import '../styles.css' import { SidebarThreadSelector } from './SidebarThreadSelector.js'; import { SidebarChat } from './SidebarChat.js'; +import { ModelSelectionSettings } from './ModelSelectionSettings.js'; +import { VoidProviderSettings } from './VoidProviderSettings.js'; +import ErrorBoundary from './ErrorBoundary.js'; const Sidebar = () => { const sidebarState = useSidebarState() const { isHistoryOpen, currentTab: tab } = sidebarState + // className='@@void-scope' return
-
+
{/* { const tabs = ['chat', 'settings', 'threadSelector'] @@ -27,21 +33,32 @@ const Sidebar = () => { sidebarStateService.setState({ currentTab: tabs[(index + 1) % tabs.length] as any }) }}>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 fb289f375..6ac2126cb 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,25 +1,89 @@ /*--------------------------------------------------------------------------------------------- * 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'; -import { useConfigState, useService, useThreadsState } from '../util/services.js'; +import { useConfigState, useService, useSidebarState, useThreadsState } from '../util/services.js'; import { generateDiffInstructions } from '../../../prompt/systemPrompts.js'; -import { userInstructionsStr } from '../../../prompt/stringifyFiles.js'; -import { CodeSelection, CodeStagingSelection } from '../../../registerThreads.js'; +import { userInstructionsStr } from '../../../prompt/stringifySelections.js'; +import { ChatMessage, CodeSelection, CodeStagingSelection } from '../../../registerThreads.js'; import { BlockCode } from '../markdown/BlockCode.js'; -import { MarkdownRender } from '../markdown/MarkdownRender.js'; +import { ChatMarkdownRender } from '../markdown/ChatMarkdownRender.js'; import { IModelService } from '../../../../../../../editor/common/services/model.js'; import { URI } from '../../../../../../../base/common/uri.js'; import { EndOfLinePreference } from '../../../../../../../editor/common/model.js'; import { IDisposable } from '../../../../../../../base/common/lifecycle.js'; -import { ErrorDisplay } from '../util/ErrorDisplay.js'; -import { LLMMessageServiceParams } from '../../../../../../../platform/void/common/llmMessageTypes.js'; +import { ErrorDisplay } from './ErrorDisplay.js'; +import { OnError, ServiceSendLLMMessageParams } from '../../../../../../../platform/void/common/llmMessageTypes.js'; +import { getCmdKey } from '../../../getCmdKey.js' +import { HistoryInputBox, InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js'; +import { VoidInputBox } from './inputs.js'; + + +const IconX = ({ size, className = '' }: { size: number, className?: string }) => { + return ( + + + + ); +}; + + +const IconArrowUp = ({ size, className = '' }: { size: number, className?: string }) => { + return ( + + + + + ); +}; + + +const IconSquare = ({ size, className = '' }: { size: number, className?: string }) => { + return ( + + + + ); +}; -// import { } from '@vscode/webview-ui-toolkit/react'; // read files from VSCode const VSReadFile = async (modelService: IModelService, uri: URI): Promise => { @@ -29,27 +93,6 @@ const VSReadFile = async (modelService: IModelService, uri: URI): Promise { // 'unixify' path pathStr = pathStr.replace(/[/\\]+/g, '/') // replace any / or \ or \\ with / @@ -62,63 +105,84 @@ export const SelectedFiles = ( | { type: 'past', selections: CodeSelection[] | null; setStaging?: undefined } | { type: 'staging', selections: CodeStagingSelection[] | null; setStaging: ((files: CodeStagingSelection[]) => void) } ) => { + + // index -> isOpened + const [selectionIsOpened, setSelectionIsOpened] = useState<(boolean)[]>(selections?.map(() => false) ?? []) + return ( !!selections && selections.length !== 0 && ( -
+
{selections.map((selection, i) => ( - - - {/* selection text */} - {type === 'staging' && selection.selectionStr && { - setStaging([...selections.slice(0, i), { ...selection, selectionStr: null }, ...selections.slice(i + 1, Infinity)]) - }} - className="btn btn-secondary btn-sm border border-vscode-input-border rounded" - >Remove - )} />} + + + } +
+ {/* selection full text */} + {type === 'staging' && selection.selectionStr && selectionIsOpened[i] && + { // clear the selection string but keep the file + // // setStaging([...selections.slice(0, i), { ...selection, selectionStr: null }, ...selections.slice(i + 1, Infinity)]) + // // }} + // onClick={() => { + // if (type !== 'staging') return + // setStaging([...selections.slice(0, i), ...selections.slice(i + 1, Infinity)]) + // }} + // className="btn btn-secondary btn-sm border border-vscode-input-border rounded" + // >Remove + // )} + /> + } - ))} + )) + }
) ) } -const ChatBubble = ({ chatMessage }: { chatMessage: ChatMessage }) => { +const ChatBubble = ({ chatMessage }: { + chatMessage: ChatMessage +}) => { const role = chatMessage.role - const children = chatMessage.displayContent - if (!children) + if (!chatMessage.displayContent) return null let chatbubbleContents: React.ReactNode @@ -126,11 +190,11 @@ const ChatBubble = ({ chatMessage }: { chatMessage: ChatMessage }) => { if (role === 'user') { chatbubbleContents = <> - {children} + {chatMessage.displayContent} } else if (role === 'assistant') { - chatbubbleContents = // sectionsHTML + chatbubbleContents = // sectionsHTML } return
@@ -144,7 +208,7 @@ const ChatBubble = ({ chatMessage }: { chatMessage: ChatMessage }) => { export const SidebarChat = () => { - const chatInputRef = useRef(null) + const inputBoxRef: React.MutableRefObject = useRef(null); const modelService = useService('modelService') @@ -154,36 +218,37 @@ 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 configState = useConfigState() - const { voidConfig } = configState + const voidConfigState = useConfigState() // threads state const threadsState = useThreadsState() const threadsStateService = useService('threadsStateService') // ----- SIDEBAR CHAT state (local) ----- - // state of current message - const [instructions, setInstructions] = useState('') // the user's instructions // state of chat - const [messageStream, setMessageStream] = useState('') + const [messageStream, setMessageStream] = useState(null) 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') + const llmMessageService = useService('llmMessageService') + // state of current message + const [instructions, setInstructions] = useState('') // the user's instructions + const onChangeText = useCallback((newStr: string) => { setInstructions(newStr) }, [setInstructions]) const isDisabled = !instructions - const formRef = useRef(null) + + const onSubmit = async (e: FormEvent) => { e.preventDefault() @@ -191,76 +256,91 @@ export const SidebarChat = () => { if (isLoading) return - const currSelns = threadsStateService.state._currentStagingSelections + + const currSelns = threadsStateService.state._currentStagingSelections ?? [] const selections = !currSelns ? null : await Promise.all( currSelns.map(async (sel) => ({ ...sel, content: await VSReadFile(modelService, sel.fileURI) })) ).then( (files) => files.filter(file => file.content !== null) as CodeSelection[] ) + + // // TODO don't save files to the thread history + // const selectedSnippets = currSelns.filter(sel => sel.selectionStr !== null) + // const selectedFiles = await Promise.all( // do not add these to the context history + // currSelns.filter(sel => sel.selectionStr === null) + // .map(async (sel) => ({ ...sel, content: await VSReadFile(modelService, sel.fileURI) })) + // ).then( + // (files) => files.filter(file => file.content !== null) as CodeSelection[] + // ) + // const contextToSendToLLM = '' + // const contextToAddToHistory = '' + + // add system message to chat history const systemPromptElt: ChatMessage = { role: 'system', content: generateDiffInstructions } threadsStateService.addMessageToCurrentThread(systemPromptElt) - const userContent = userInstructionsStr(instructions, selections) - const newHistoryElt: ChatMessage = { role: 'user', content: userContent, displayContent: instructions, selections } - threadsStateService.addMessageToCurrentThread(newHistoryElt) + // add user's message to chat history + const userHistoryElt: ChatMessage = { role: 'user', content: userInstructionsStr(instructions, selections), displayContent: instructions, selections: selections } + threadsStateService.addMessageToCurrentThread(userHistoryElt) 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 = { + const object: ServiceSendLLMMessageParams = { logging: { loggingName: 'Chat' }, - messages: [...(currentThread?.messages ?? []).map(m => ({ role: m.role, content: m.content })),], + messages: [...(currentThread?.messages ?? []).map(m => ({ role: m.role, content: m.content || '(null)' })),], onText: ({ newText, fullText }) => setMessageStream(fullText), onFinalMessage: ({ fullText: content }) => { console.log('chat: running final message') // add assistant's message to chat history, and clear selection - const newHistoryElt: ChatMessage = { role: 'assistant', content, displayContent: content } - threadsStateService.addMessageToCurrentThread(newHistoryElt) - setMessageStream('') + const assistantHistoryElt: ChatMessage = { role: 'assistant', content, displayContent: content || null } + threadsStateService.addMessageToCurrentThread(assistantHistoryElt) + 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 - const newHistoryElt: ChatMessage = { role: 'assistant', content, displayContent: content, } - threadsStateService.addMessageToCurrentThread(newHistoryElt) + let content = messageStream ?? ''; // just use the current content + const assistantHistoryElt: ChatMessage = { role: 'assistant', content, displayContent: content || null, } + threadsStateService.addMessageToCurrentThread(assistantHistoryElt) setMessageStream('') setIsLoading(false) - setLatestError(error) + setLatestError({ message, fullError }) }, - voidConfig, + featureName: 'Ctrl+L', + } - const latestRequestId = sendLLMMessageService.sendLLMMessage(object) + const latestRequestId = llmMessageService.sendLLMMessage(object) latestRequestIdRef.current = latestRequestId - - setIsLoading(true) - setInstructions(''); - formRef.current?.reset(); // reset the form's text when clear instructions or unexpected behavior happens threadsStateService.setStaging([]) // clear staging - setLatestError(null) } const onAbort = () => { - // abort the LLM + // abort the LLM call if (latestRequestIdRef.current) - sendLLMMessageService.abort(latestRequestIdRef.current) + llmMessageService.abort(latestRequestIdRef.current) // if messageStream was not empty, add it to the history - const llmContent = messageStream || '(null)' - const newHistoryElt: ChatMessage = { role: 'assistant', content: llmContent, displayContent: messageStream, } - threadsStateService.addMessageToCurrentThread(newHistoryElt) + const llmContent = messageStream ?? '' + const assistantHistoryElt: ChatMessage = { role: 'assistant', content: llmContent, displayContent: messageStream || null, } + threadsStateService.addMessageToCurrentThread(assistantHistoryElt) setMessageStream('') setIsLoading(false) @@ -278,77 +358,84 @@ export const SidebarChat = () => { {currentThread !== null && currentThread?.messages.map((message, i) => )} + {/* message stream */} - +
- {/* chatbar */} -
- {/* selection */} -
-
-
- {/* selections */} - {(selections && selections.length !== 0) &&
- -
} - -
{ if (e.key === 'Enter' && !e.shiftKey) onSubmit(e) }} - - onSubmit={(e) => { - console.log('submit!') - onSubmit(e) - }}> - {/* input */} - -