Compare commits

...

63 Commits

Author SHA1 Message Date
Andrew Pareles 6f55e6900b Merge pull request #413 from voideditor/model-selection
1.2.5 misc
2025-04-16 22:57:50 -07:00
Andrew Pareles 36e10dcc08 1.2.5 2025-04-16 22:56:52 -07:00
Andrew Pareles ce8c790e5c undo version 2025-04-16 22:47:52 -07:00
Mathew Pareles 738067951a ux 2025-04-16 22:31:28 -07:00
Andrew Pareles 8b1a42499b add more boundaries 2025-04-16 21:56:23 -07:00
Mathew Pareles 7fd52b4805 fix commandbarinchat state 2025-04-16 20:44:47 -07:00
Mathew Pareles 5f30506f46 terminal tool style 2025-04-16 20:44:47 -07:00
Andrew Pareles 48efc32dd3 lint finite size 2025-04-16 20:25:12 -07:00
Andrew Pareles 5fffd2fe0f rename command tool 2025-04-16 20:23:52 -07:00
Andrew Pareles 664428c8f0 add download chat buttons 2025-04-16 18:09:03 -07:00
Andrew Pareles aa8b999da0 Merge pull request #409 from voideditor/model-selection
prompt update for o3
2025-04-16 14:31:10 -07:00
Andrew Pareles d99d2cca4a prompt update for o3 2025-04-16 14:30:49 -07:00
Andrew Pareles e7da1d27bd Merge pull request #408 from voideditor/model-selection
o3 fallback
2025-04-16 10:47:31 -07:00
Andrew Pareles 56c50dc218 fallback 2025-04-16 10:46:37 -07:00
Andrew Pareles e0e7b360fe Merge pull request #407 from voideditor/model-selection
Model selection
2025-04-16 10:41:03 -07:00
Andrew Pareles 434a8289d9 1.2.4 bump 2025-04-16 10:40:37 -07:00
Andrew Pareles 7e0e65d3b7 o3, o4-mini 2025-04-16 10:39:37 -07:00
Andrew Pareles 4cb96cb60e Merge pull request #403 from Dylan-86/patch-2
Fix AppImage creation bug
2025-04-16 04:30:39 -07:00
Andrew Pareles 4a7ea3ec43 appimage 2025-04-16 04:30:01 -07:00
Andrew Pareles bd53772523 Merge remote-tracking branch 'origin/main' into pr/Dylan-86/403 2025-04-16 04:29:58 -07:00
Andrew Pareles f09f538b05 Merge pull request #406 from voideditor/model-selection
Onboarding, SSH
2025-04-16 04:27:13 -07:00
Andrew Pareles ff2ed836c7 onboarding style 2025-04-16 04:26:35 -07:00
Mathew Pareles 6db29526ec add letterpress image 2025-04-16 04:04:50 -07:00
Andrew Pareles 27d9cc2916 comment 2025-04-16 03:50:56 -07:00
Andrew Pareles dbbaa61961 try again 2025-04-16 03:49:41 -07:00
Andrew Pareles 05bd1790a7 mistral 2025-04-16 03:46:14 -07:00
Mathew Pareles 4b30990e92 fix long api key 2025-04-16 03:35:25 -07:00
Mathew Pareles 3a0eedc9fa py 2025-04-16 03:29:15 -07:00
Mathew Pareles 64d4e264bf onboarding 2025-04-16 03:27:44 -07:00
Mathew Pareles afc5cf512b remove tooltip for now 2025-04-16 03:21:14 -07:00
Mathew Pareles 35e8cf59ff better ending screen onboarding 2025-04-16 03:14:52 -07:00
Mathew Pareles fd4ecaff8a fz 2025-04-16 02:51:42 -07:00
Mathew Pareles 55c4f9e1cf onboarding decent final draft 2025-04-16 02:47:19 -07:00
Andrew Pareles 78f9463da1 .voidinstructions 2025-04-16 02:27:29 -07:00
Andrew Pareles 23f1e8c641 .voidinstructions file 2025-04-16 02:21:36 -07:00
Andrew Pareles 79eacad18b error boundaries everywhere + misc (BROKEN) 2025-04-16 02:21:17 -07:00
Andrew Pareles 3e0e0bd4aa SSH buttons 2025-04-16 01:23:25 -07:00
Andrew Pareles fece3b2548 update settings page 2025-04-16 01:18:59 -07:00
Andrew Pareles 091855a89a linux version for deb, rem, snap 2025-04-16 01:02:57 -07:00
Andrew Pareles b3bd454bad add SSH button 2025-04-16 01:02:57 -07:00
Mathew Pareles bd580946ce add image 2025-04-16 00:58:24 -07:00
Mathew Pareles 3bd6d08841 button 2025-04-16 00:58:24 -07:00
Andrew Pareles 152cfd93aa Merge pull request #404 from voideditor/model-selection
1.2.1 updates
2025-04-15 23:09:44 -07:00
Andrew Pareles 7ea4e16f5f 1.2.3 2025-04-15 23:08:26 -07:00
Andrew Pareles 884548615b checkpoint UX and dropdown UI 2025-04-15 23:06:13 -07:00
Mathew Pareles b62943fffd tooltip for ollama 2025-04-15 18:35:23 -07:00
Andrew Pareles 9bd97d0cd5 Claude reasoning small fix 2025-04-15 17:30:35 -07:00
Andrew Pareles bf9852858e remove auto-selection of current file 2025-04-15 17:30:21 -07:00
Andrew Pareles 6058b7ff78 misc fixes and gemini prompt improvement 2025-04-15 17:04:06 -07:00
Mathew Pareles 9b8f3ec2e2 desc 2025-04-15 16:52:21 -07:00
Mathew Pareles d3fd5be294 small 2025-04-15 16:38:54 -07:00
Mathew Pareles aa4398c734 small changes 2025-04-15 16:36:46 -07:00
Mathew Pareles 29d1a919be small styles 2025-04-15 16:16:04 -07:00
Andrew Pareles feea5e13f3 update service fix 2025-04-15 15:56:15 -07:00
Mathew Pareles 119db73f69 colors 2025-04-15 15:12:40 -07:00
Andrew Pareles f59b24496e 1.2.2 fix gemini 2025-04-15 15:01:24 -07:00
Andrew Pareles a2ae9794ed eslint 2025-04-15 15:00:44 -07:00
Andrew Pareles f335ce7204 fix gemini 2025-04-15 14:59:32 -07:00
Mathew Pareles 0ddab03f8b styles 2025-04-15 03:23:31 -07:00
Mathew Pareles 5f8fddb01e fix flash 2025-04-15 03:23:31 -07:00
Mathew Pareles c81f628b7b colors 2025-04-15 03:23:31 -07:00
Mathew Pareles 9d1ab8728a styles 2025-04-15 03:23:31 -07:00
Dylan-86 7a0f9ac77c Fix AppImage creation bug
See https://github.com/voideditor/void/issues/402
2025-04-15 12:22:15 +02:00
36 changed files with 1812 additions and 851 deletions
+574 -16
View File
@@ -16,6 +16,7 @@
"@microsoft/1ds-core-js": "^3.2.13",
"@microsoft/1ds-post-js": "^3.2.13",
"@mistralai/mistralai": "^1.5.0",
"@modelcontextprotocol/sdk": "^1.9.0",
"@parcel/watcher": "2.5.1",
"@types/semver": "^7.5.8",
"@vscode/deviceid": "^0.1.1",
@@ -2551,6 +2552,27 @@
"zod": ">= 3"
}
},
"node_modules/@modelcontextprotocol/sdk": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.9.0.tgz",
"integrity": "sha512-Jq2EUCQpe0iyO5FGpzVYDNFR6oR53AIrwph9yWl7uSc7IWUMsrmpmSaTGra5hQNunXpM+9oit85p924jWuHzUA==",
"license": "MIT",
"dependencies": {
"content-type": "^1.0.5",
"cors": "^2.8.5",
"cross-spawn": "^7.0.3",
"eventsource": "^3.0.2",
"express": "^5.0.1",
"express-rate-limit": "^7.5.0",
"pkce-challenge": "^5.0.0",
"raw-body": "^3.0.0",
"zod": "^3.23.8",
"zod-to-json-schema": "^3.24.1"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@next/env": {
"version": "15.2.0",
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.2.0.tgz",
@@ -6014,6 +6036,70 @@
"node": "0.4 || >=0.5.8"
}
},
"node_modules/body-parser": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
"integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
"license": "MIT",
"dependencies": {
"bytes": "^3.1.2",
"content-type": "^1.0.5",
"debug": "^4.4.0",
"http-errors": "^2.0.0",
"iconv-lite": "^0.6.3",
"on-finished": "^2.4.1",
"qs": "^6.14.0",
"raw-body": "^3.0.0",
"type-is": "^2.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/body-parser/node_modules/media-typer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
"integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/body-parser/node_modules/mime-db": {
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/body-parser/node_modules/mime-types": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
"license": "MIT",
"dependencies": {
"mime-db": "^1.54.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/body-parser/node_modules/type-is": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
"integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
"license": "MIT",
"dependencies": {
"content-type": "^1.0.5",
"media-typer": "^1.1.0",
"mime-types": "^3.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
@@ -6173,7 +6259,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"dev": true,
"engines": {
"node": ">= 0.8"
}
@@ -7058,7 +7143,6 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"dev": true,
"engines": {
"node": ">= 0.6"
}
@@ -7076,11 +7160,19 @@
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"dev": true,
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
"integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
"license": "MIT",
"engines": {
"node": ">=6.6.0"
}
},
"node_modules/cookies": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz",
@@ -7186,6 +7278,19 @@
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
"dev": true
},
"node_modules/cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
"license": "MIT",
"dependencies": {
"object-assign": "^4",
"vary": "^1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/create-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
@@ -7723,7 +7828,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"dev": true,
"engines": {
"node": ">= 0.8"
}
@@ -8011,8 +8115,7 @@
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"dev": true
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
},
"node_modules/electron": {
"version": "34.3.2",
@@ -8420,8 +8523,7 @@
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"dev": true
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
},
"node_modules/escape-string-regexp": {
"version": "4.0.0",
@@ -8768,6 +8870,15 @@
"node": ">=0.10.0"
}
},
"node_modules/etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/event-emitter": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
@@ -8811,6 +8922,27 @@
"node": ">=0.8.x"
}
},
"node_modules/eventsource": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.6.tgz",
"integrity": "sha512-l19WpE2m9hSuyP06+FbuUUf1G+R0SFLrtQfbRb9PRr+oimOfxQhgGCbVaXg5IvZyyTThJsxh6L/srkMiCeBPDA==",
"license": "MIT",
"dependencies": {
"eventsource-parser": "^3.0.1"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/eventsource-parser": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.1.tgz",
"integrity": "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/expand-brackets": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
@@ -8961,6 +9093,179 @@
"node": ">=0.10.0"
}
},
"node_modules/express": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
"license": "MIT",
"dependencies": {
"accepts": "^2.0.0",
"body-parser": "^2.2.0",
"content-disposition": "^1.0.0",
"content-type": "^1.0.5",
"cookie": "^0.7.1",
"cookie-signature": "^1.2.1",
"debug": "^4.4.0",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"etag": "^1.8.1",
"finalhandler": "^2.1.0",
"fresh": "^2.0.0",
"http-errors": "^2.0.0",
"merge-descriptors": "^2.0.0",
"mime-types": "^3.0.0",
"on-finished": "^2.4.1",
"once": "^1.4.0",
"parseurl": "^1.3.3",
"proxy-addr": "^2.0.7",
"qs": "^6.14.0",
"range-parser": "^1.2.1",
"router": "^2.2.0",
"send": "^1.1.0",
"serve-static": "^2.2.0",
"statuses": "^2.0.1",
"type-is": "^2.0.1",
"vary": "^1.1.2"
},
"engines": {
"node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/express-rate-limit": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz",
"integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==",
"license": "MIT",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/express-rate-limit"
},
"peerDependencies": {
"express": "^4.11 || 5 || ^5.0.0-beta.1"
}
},
"node_modules/express/node_modules/accepts": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
"integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
"license": "MIT",
"dependencies": {
"mime-types": "^3.0.0",
"negotiator": "^1.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express/node_modules/content-disposition": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
"integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
"license": "MIT",
"dependencies": {
"safe-buffer": "5.2.1"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express/node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/express/node_modules/fresh": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
"integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/express/node_modules/media-typer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
"integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/express/node_modules/mime-db": {
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express/node_modules/mime-types": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
"license": "MIT",
"dependencies": {
"mime-db": "^1.54.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express/node_modules/negotiator": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express/node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/express/node_modules/type-is": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
"integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
"license": "MIT",
"dependencies": {
"content-type": "^1.0.5",
"media-typer": "^1.1.0",
"mime-types": "^3.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/ext": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz",
@@ -9236,6 +9541,32 @@
"node": ">=8"
}
},
"node_modules/finalhandler": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
"integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.0",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"on-finished": "^2.4.1",
"parseurl": "^1.3.3",
"statuses": "^2.0.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/finalhandler/node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/find-parent-dir": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/find-parent-dir/-/find-parent-dir-0.3.1.tgz",
@@ -9656,6 +9987,15 @@
"node": ">= 12.20"
}
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/fraction.js": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
@@ -12195,7 +12535,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"depd": "2.0.0",
@@ -12340,6 +12679,18 @@
"node": ">=0.8.0"
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/icss-utils": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz",
@@ -12511,6 +12862,15 @@
"node": ">= 12"
}
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
"license": "MIT",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/is-absolute": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz",
@@ -14600,6 +14960,18 @@
"node": ">= 0.10.0"
}
},
"node_modules/merge-descriptors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
"integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/merge-options": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-options/-/merge-options-1.0.1.tgz",
@@ -15990,7 +16362,6 @@
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
"dev": true,
"dependencies": {
"ee-first": "1.1.1"
},
@@ -16460,7 +16831,6 @@
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
"dev": true,
"engines": {
"node": ">= 0.8"
}
@@ -16699,6 +17069,15 @@
"node": ">= 6"
}
},
"node_modules/pkce-challenge": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz",
"integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==",
"license": "MIT",
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/pkg-dir": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
@@ -17802,6 +18181,19 @@
"integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==",
"dev": true
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
"license": "MIT",
"dependencies": {
"forwarded": "0.2.0",
"ipaddr.js": "1.9.1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
@@ -17910,6 +18302,21 @@
"node": ">=6"
}
},
"node_modules/qs": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/querystringify": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
@@ -17952,6 +18359,30 @@
"safe-buffer": "^5.1.0"
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/raw-body": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
"integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
"license": "MIT",
"dependencies": {
"bytes": "3.1.2",
"http-errors": "2.0.0",
"iconv-lite": "0.6.3",
"unpipe": "1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/rc": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
@@ -18730,6 +19161,37 @@
"fsevents": "~2.3.2"
}
},
"node_modules/router": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
"integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.0",
"depd": "^2.0.0",
"is-promise": "^4.0.0",
"parseurl": "^1.3.3",
"path-to-regexp": "^8.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/router/node_modules/is-promise": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
"license": "MIT"
},
"node_modules/router/node_modules/path-to-regexp": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
"integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
"license": "MIT",
"engines": {
"node": ">=16"
}
},
"node_modules/run-parallel": {
"version": "1.1.10",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.10.tgz",
@@ -18828,6 +19290,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/sax": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
@@ -18953,6 +19421,67 @@
"node": ">= 0.10"
}
},
"node_modules/send": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
"integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
"license": "MIT",
"dependencies": {
"debug": "^4.3.5",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"etag": "^1.8.1",
"fresh": "^2.0.0",
"http-errors": "^2.0.0",
"mime-types": "^3.0.1",
"ms": "^2.1.3",
"on-finished": "^2.4.1",
"range-parser": "^1.2.1",
"statuses": "^2.0.1"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/send/node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/send/node_modules/fresh": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
"integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/send/node_modules/mime-db": {
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/send/node_modules/mime-types": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
"license": "MIT",
"dependencies": {
"mime-db": "^1.54.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/serialize-error": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz",
@@ -18992,6 +19521,30 @@
"randombytes": "^2.1.0"
}
},
"node_modules/serve-static": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
"integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
"license": "MIT",
"dependencies": {
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"parseurl": "^1.3.3",
"send": "^1.2.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/serve-static/node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
@@ -19100,8 +19653,7 @@
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"dev": true
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
},
"node_modules/shallow-clone": {
"version": "3.0.1",
@@ -19905,7 +20457,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.8"
@@ -21269,7 +21820,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"dev": true,
"engines": {
"node": ">=0.6"
}
@@ -22031,6 +22581,15 @@
"node": ">= 10.0.0"
}
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/unset-value": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
@@ -22254,7 +22813,6 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"dev": true,
"engines": {
"node": ">= 0.8"
}
+1
View File
@@ -77,6 +77,7 @@
"@microsoft/1ds-core-js": "^3.2.13",
"@microsoft/1ds-post-js": "^3.2.13",
"@mistralai/mistralai": "^1.5.0",
"@modelcontextprotocol/sdk": "^1.9.0",
"@parcel/watcher": "2.5.1",
"@types/semver": "^7.5.8",
"@vscode/deviceid": "^0.1.1",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"nameShort": "Void",
"nameLong": "Void",
"voidVersion": "1.2.1",
"voidVersion": "1.2.5",
"applicationName": "void",
"dataFolderName": ".void-editor",
"win32MutexName": "voideditor",
@@ -34,11 +34,8 @@ mkdir -pv "$APP_DIR/usr/share/icons/hicolor/256x256/apps"
# Exclude create-appimage.sh and appimagetool-x86_64.AppImage from being copied
echo "Copying files excluding create-appimage.sh and appimagetool-x86_64.AppImage..."
for file in ./*; do
if [[ "$file" != "./create-appimage.sh" && "$file" != "./appimagetool-x86_64.AppImage" ]]; then
cp -rv "$file" "$APP_DIR/usr/bin/"
fi
done
cp -v ./void "$APP_DIR/usr/bin/"
# Copy the icon to required locations
cp -v ./void.png "$APP_DIR/void.png"
+13
View File
@@ -1,3 +1,16 @@
# README
This is a community-made AppImage creation script.
There are some reported bugs with it.
To generate an AppImage yourself, feel free to look at
stable-linux.yml in the separate `void-builder/` repo,
which runs a GitHub Action that builds the AppImage you see on our website.
# Void AppImage Creation Script
This script automates the process of creating an AppImage for the Void Editor using Docker. It works on macOS and Linux platforms.
@@ -22,10 +22,12 @@ import { ColorScheme } from '../../web.api.js';
import { OpenFileFolderAction, OpenFolderAction } from '../../actions/workspaceActions.js';
import { IWindowOpenable } from '../../../../platform/window/common/window.js';
import { splitRecentLabel } from '../../../../base/common/labels.js';
import { IViewsService } from '../../../services/views/common/viewsService.js';
/* eslint-disable */ // Void
import { VOID_CTRL_K_ACTION_ID, VOID_CTRL_L_ACTION_ID } from '../../../contrib/void/browser/actionIDs.js';
import { VOID_OPEN_SETTINGS_ACTION_ID } from '../../../contrib/void/browser/voidSettingsPane.js';
import { VIEWLET_ID as REMOTE_EXPLORER_VIEWLET_ID } from '../../../contrib/remote/browser/remoteExplorer.js';
/* eslint-enable */
// interface WatermarkEntry {
@@ -98,6 +100,7 @@ export class EditorGroupWatermark extends Disposable {
@ICommandService private readonly commandService: ICommandService,
@IHostService private readonly hostService: IHostService,
@ILabelService private readonly labelService: ILabelService,
@IViewsService private readonly viewsService: IViewsService,
) {
super();
@@ -182,14 +185,21 @@ export class EditorGroupWatermark extends Disposable {
// Void - if the workbench is empty, show open
if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
// Create a flex container for buttons with vertical direction
const buttonContainer = $('div');
buttonContainer.style.display = 'flex';
buttonContainer.style.flexDirection = 'column'; // Change to column for vertical stacking
buttonContainer.style.alignItems = 'center'; // Center the buttons horizontally
buttonContainer.style.gap = '8px'; // Reduce gap between buttons from 16px to 8px
buttonContainer.style.marginBottom = '16px';
voidIconBox.appendChild(buttonContainer);
// Open a folder
const openFolderButton = h('button')
openFolderButton.root.classList.add('void-watermark-button')
openFolderButton.root.classList.add('void-openfolder-button')
openFolderButton.root.style.display = 'block'
openFolderButton.root.style.marginLeft = 'auto'
openFolderButton.root.style.marginRight = 'auto'
openFolderButton.root.style.marginBottom = '16px'
openFolderButton.root.textContent = 'Open a folder'
openFolderButton.root.style.width = '124px' // Set width to 124px as requested
openFolderButton.root.textContent = 'Open Folder'
openFolderButton.root.onclick = () => {
this.commandService.executeCommand(isMacintosh && isNative ? OpenFileFolderAction.ID : OpenFolderAction.ID)
// if (this.contextKeyService.contextMatchesRules(ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('workspace')))) {
@@ -198,7 +208,19 @@ export class EditorGroupWatermark extends Disposable {
// this.commandService.executeCommand(isMacintosh ? 'workbench.action.files.openFileFolder' : 'workbench.action.files.openFolder');
// }
}
voidIconBox.appendChild(openFolderButton.root);
buttonContainer.appendChild(openFolderButton.root);
// Open SSH button
const openSSHButton = h('button')
openSSHButton.root.classList.add('void-openssh-button')
openSSHButton.root.style.display = 'block'
openSSHButton.root.style.backgroundColor = '#5a5a5a' // Made darker than the default gray
openSSHButton.root.style.width = '124px' // Set width to 124px as requested
openSSHButton.root.textContent = 'Open SSH'
openSSHButton.root.onclick = () => {
this.viewsService.openViewContainer(REMOTE_EXPLORER_VIEWLET_ID);
}
buttonContainer.appendChild(openSSHButton.root);
// Recents
@@ -244,6 +266,9 @@ export class EditorGroupWatermark extends Disposable {
const dirSpan = $('span');
dirSpan.style.paddingLeft = '4px';
dirSpan.style.whiteSpace = 'nowrap';
dirSpan.style.overflow = 'hidden';
dirSpan.style.maxWidth = '300px';
dirSpan.innerText = parentPath;
dirSpan.title = fullPath;
@@ -50,6 +50,7 @@
}
/* light */
.void-void-icon,
.monaco-workbench .part.editor > .content .editor-group-container > .editor-group-watermark > .letterpress {
width: 100%;
max-height: 100%;
@@ -60,14 +61,17 @@
background-repeat: no-repeat;
}
.void-void-icon,
.monaco-workbench.vs-dark .part.editor > .content .editor-group-container .editor-group-watermark > .letterpress {
background-image: url('./void_cube_noshadow.png'); /* // Void */
}
.void-void-icon,
.monaco-workbench.hc-light .part.editor > .content .editor-group-container .editor-group-watermark > .letterpress {
background-image: url('./void_cube_noshadow.png'); /* // Void */
}
.void-void-icon,
.monaco-workbench.hc-black .part.editor > .content .editor-group-container .editor-group-watermark > .letterpress {
background-image: url('./void_cube_noshadow.png'); /* // Void */
}
@@ -792,7 +792,6 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
const featureName: FeatureName = 'Autocomplete'
const modelSelection = this._settingsService.state.modelSelectionOfFeature[featureName]
const modelSelectionOptions = modelSelection ? this._settingsService.state.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName] : undefined
const aiInstructions = this._settingsService.state.globalSettings.aiInstructions
// set parameters of `newAutocompletion` appropriately
newAutocompletion.llmPromise = new Promise((resolve, reject) => {
@@ -804,8 +803,7 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
prefix: llmPrefix,
suffix: llmSuffix,
stopTokens: stopTokens,
},
aiInstructions
}
}),
modelSelection,
modelSelectionOptions,
@@ -25,8 +25,6 @@ import { Position } from '../../../../editor/common/core/position.js';
import { IMetricsService } from '../common/metricsService.js';
import { shorten } from '../../../../base/common/labels.js';
import { IVoidModelService } from '../common/voidModelService.js';
import { IEditorService } from '../../../services/editor/common/editorService.js';
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
import { findLast, findLastIdx } from '../../../../base/common/arraysFind.js';
import { IEditCodeService } from './editCodeServiceInterface.js';
import { VoidFileSnapshot } from '../common/editCodeServiceTypes.js';
@@ -246,8 +244,6 @@ class ChatThreadService extends Disposable implements IChatThreadService {
@IVoidSettingsService private readonly _settingsService: IVoidSettingsService,
@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
@IMetricsService private readonly _metricsService: IMetricsService,
@IEditorService private readonly _editorService: IEditorService,
@ICodeEditorService private readonly _codeEditorService: ICodeEditorService,
@IEditCodeService private readonly _editCodeService: IEditCodeService,
@INotificationService private readonly _notificationService: INotificationService,
@IConvertToLLMMessageService private readonly _convertToLLMMessagesService: IConvertToLLMMessageService,
@@ -266,9 +262,6 @@ class ChatThreadService extends Disposable implements IChatThreadService {
// always be in a thread
this.openNewThread()
// when the user changes files, automatically add the new file as a stagingSelection
this._register(this._editorService.onDidActiveEditorChange(() => this._addCurrentFileAsStagingSelectionDuringFileChange()));
// keep track of user-modified files
// const disposablesOfModelId: { [modelId: string]: IDisposable[] } = {}
@@ -288,40 +281,6 @@ class ChatThreadService extends Disposable implements IChatThreadService {
}
// add the current file to the thread being edited
private _addCurrentFileAsStagingSelectionDuringFileChange() {
const newModel = this._codeEditorService.getActiveCodeEditor()?.getModel() ?? null
if (!newModel) { return }
const isCurrentlyFocusing = this.isCurrentlyFocusingMessage()
if (isCurrentlyFocusing) return
// only add if the user hasn't sent a message yet
if (this.getCurrentThread().messages.length !== 0) return
const newStagingSelection: StagingSelectionItem = {
type: 'File',
uri: newModel.uri,
language: newModel.getLanguageId(),
state: { wasAddedAsCurrentFile: true }
}
const oldStagingSelections = this.getCurrentThreadState().stagingSelections || [];
// remove all old selectons that are marked as `wasAddedAsCurrentFile`
const newStagingSelections: StagingSelectionItem[] = oldStagingSelections.filter(s => s.state && !s.state.wasAddedAsCurrentFile)
const fileIsAlreadyHere = oldStagingSelections.some(s => s.type === 'File' && s.uri.fsPath === newStagingSelection.uri.fsPath)
if (!fileIsAlreadyHere) {
newStagingSelections.push(newStagingSelection)
}
this.setCurrentThreadState({ stagingSelections: newStagingSelections });
}
// !!! this is important for properly restoring URIs from storage
// should probably re-use code from void/src/vs/base/common/marshalling.ts instead. but this is simple enough
private _convertThreadDataFromStorage(threadsStr: string): ChatThreads {
@@ -390,36 +349,6 @@ class ChatThreadService extends Disposable implements IChatThreadService {
editUserMessageAndStreamResponse: IChatThreadService['editUserMessageAndStreamResponse'] = async ({ userMessage, messageIdx, threadId }) => {
const thread = this.state.allThreads[threadId]
if (!thread) return // should never happen
if (thread.messages?.[messageIdx]?.role !== 'user') {
throw new Error(`Error: editing a message with role !=='user'`)
}
// get prev and curr selections before clearing the message
const currSelns = thread.messages[messageIdx].state.stagingSelections || [] // staging selections for the edited message
// clear messages up to the index
const slicedMessages = thread.messages.slice(0, messageIdx)
this._setState({
allThreads: {
...this.state.allThreads,
[thread.id]: {
...thread,
messages: slicedMessages
}
}
}, true)
// re-add the message and stream it
this.addUserMessageAndStreamResponse({ userMessage, _chatSelections: currSelns, threadId })
}
private _currentModelSelectionProps = () => {
// these settings should not change throughout the loop (eg anthropic breaks if you change its thinking mode and it's using tools)
const featureName: FeatureName = 'Chat'
@@ -924,7 +853,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
const [_, toIdx] = c
if (toIdx === fromIdx) return
// console.log(`going from ${fromIdx} to ${toIdx}`)
console.log(`going from ${fromIdx} to ${toIdx}`)
// update the user's checkpoint
this._addUserModificationsToCurrCheckpoint({ threadId })
@@ -936,15 +865,14 @@ A,B,C are all files.
x means a checkpoint where the file changed.
A B C D E F G H I
x x x x x x x x x
| | | | | | | | |
x | | | | | | | x
---x-|-|-|-x-|-x-|----- <-- to
x | | | | | x
| | x x |
| | | |
-------x-|---x-x------- <-- from
x
x x x x x x <-- you can't always go up to find the "before" version; sometimes you need to go down
| | | | | | x
--x-|-|-|-x---x-|----- <-- to
| | | | x x
| | x x |
| | | |
----x-|---x-x------- <-- from
x
We need to revert anything that happened between to+1 and from.
**We do this by finding the last x from 0...`to` for each file and applying those contents.**
@@ -952,9 +880,19 @@ We only need to do it for files that were edited since `to`, ie files between to
*/
if (toIdx < fromIdx) {
const { lastIdxOfURI } = this._getCheckpointsBetween({ threadId, loIdx: toIdx + 1, hiIdx: fromIdx })
const idxes = function* () {
for (let k = toIdx; k >= 0; k -= 1) { // first go up
yield k
}
for (let k = toIdx + 1; k < thread.messages.length; k += 1) { // then go down
yield k
}
}
for (const fsPath in lastIdxOfURI) {
// apply lowest down content for each uri (or original if not found)
for (let k = toIdx; k >= 0; k -= 1) {
// find the first instance of this file starting at toIdx (go up to latest file; if there is none, go down)
for (const k of idxes()) {
const message = thread.messages[k]
if (message.role !== 'checkpoint') continue
const res = this._getCheckpointInfo(message, fsPath, { includeUserModifiedChanges: jumpToUserModified })
@@ -970,16 +908,16 @@ We only need to do it for files that were edited since `to`, ie files between to
/*
if redoing
A B C D E F G H I
x x x x x x x x x
| | | | | | | | |
x | | | | | | | x
---x-|-|-|-x-|-x-|----- <-- from
x | | | | | x
| | x x |
| | | |
-------x-|---x-x------- <-- to
x
A B C D E F G H I J
x x x x x x x
| | | | | | x x x
--x-|-|-|-x---x-|-|--- <-- from
| | | | x x
| | x x |
| | | |
----x-|---x-x-----|--- <-- to
x x
We need to apply latest change for anything that happened between from+1 and to.
We only need to do it for files that were edited since `from`, ie files between from+1...to.
@@ -995,7 +933,6 @@ We only need to do it for files that were edited since `from`, ie files between
if (!res) continue
const { voidFileSnapshot } = res
if (!voidFileSnapshot) continue
this._editCodeService.restoreVoidFileSnapshot(URI.file(fsPath), voidFileSnapshot)
break
}
@@ -1044,11 +981,15 @@ We only need to do it for files that were edited since `from`, ie files between
})
}
async addUserMessageAndStreamResponse({ userMessage, _chatSelections, threadId }: { userMessage: string, _chatSelections?: StagingSelectionItem[], threadId: string }) {
dismissStreamError(threadId: string): void {
this._setStreamState(threadId, { error: undefined }, 'merge')
}
private async _addUserMessageAndStreamResponse({ userMessage, _chatSelections, threadId }: { userMessage: string, _chatSelections?: StagingSelectionItem[], threadId: string }) {
const thread = this.state.allThreads[threadId]
if (!thread) return // should never happen
const llmCancelToken = this.streamState[threadId]?.streamingToken // currently streaming LLM on this thread
if (llmCancelToken === undefined && this.streamState[threadId]?.isRunning === 'LLM') {
// if about to call the other LLM, just wait for it by stopping right now
@@ -1057,14 +998,11 @@ We only need to do it for files that were edited since `from`, ie files between
// stop it (this simply resolves the promise to free up space)
if (llmCancelToken !== undefined) this._llmMessageService.abort(llmCancelToken)
// add dummy before this message to keep checkpoint before user message idea consistent
if (thread.messages.length === 0) {
this._addUserCheckpoint({ threadId })
}
const { chatMode } = this._settingsService.state.globalSettings
// add user's message to chat history
@@ -1084,11 +1022,61 @@ We only need to do it for files that were edited since `from`, ie files between
)
}
dismissStreamError(threadId: string): void {
this._setStreamState(threadId, { error: undefined }, 'merge')
async addUserMessageAndStreamResponse({ userMessage, _chatSelections, threadId }: { userMessage: string, _chatSelections?: StagingSelectionItem[], threadId: string }) {
const thread = this.state.allThreads[threadId];
if (!thread) return
// if there's a current checkpoint, delete all messages after it
if (thread.state.currCheckpointIdx !== null) {
const checkpointIdx = thread.state.currCheckpointIdx;
const newMessages = thread.messages.slice(0, checkpointIdx + 1);
// Update the thread with truncated messages
const newThreads = {
...this.state.allThreads,
[threadId]: {
...thread,
lastModified: new Date().toISOString(),
messages: newMessages,
}
};
this._storeAllThreads(newThreads);
this._setState({ allThreads: newThreads }, true);
}
// Now call the original method to add the user message and stream the response
await this._addUserMessageAndStreamResponse({ userMessage, _chatSelections, threadId });
}
editUserMessageAndStreamResponse: IChatThreadService['editUserMessageAndStreamResponse'] = async ({ userMessage, messageIdx, threadId }) => {
const thread = this.state.allThreads[threadId]
if (!thread) return // should never happen
if (thread.messages?.[messageIdx]?.role !== 'user') {
throw new Error(`Error: editing a message with role !=='user'`)
}
// get prev and curr selections before clearing the message
const currSelns = thread.messages[messageIdx].state.stagingSelections || [] // staging selections for the edited message
// clear messages up to the index
const slicedMessages = thread.messages.slice(0, messageIdx)
this._setState({
allThreads: {
...this.state.allThreads,
[thread.id]: {
...thread,
messages: slicedMessages
}
}
}, true)
// re-add the message and stream it
this._addUserMessageAndStreamResponse({ userMessage, _chatSelections: currSelns, threadId })
}
// ---------- the rest ----------
@@ -1386,21 +1374,6 @@ We only need to do it for files that were edited since `from`, ie files between
// switch to the thread
this.switchToThread(threadId)
// add the current file as a staging selection
const model = this._codeEditorService.getActiveCodeEditor()?.getModel()
if (model) {
this._setThreadState(this.state.currentThreadId, {
stagingSelections: [{
type: 'File',
uri: model.uri,
language: model.getLanguageId(),
state: {
wasAddedAsCurrentFile: true
}
}]
})
}
return;
}
}
// otherwise, start a new thread
@@ -7,12 +7,14 @@ import { IWorkspaceContextService } from '../../../../platform/workspace/common/
import { IEditorService } from '../../../services/editor/common/editorService.js';
import { ChatMessage } from '../common/chatThreadServiceTypes.js';
import { getIsReasoningEnabledState, getMaxOutputTokens, getModelCapabilities } from '../common/modelCapabilities.js';
import { toolCallXMLStr, chat_systemMessage, ToolName } from '../common/prompt/prompts.js';
import { reParsedToolXMLString, chat_systemMessage, ToolName } from '../common/prompt/prompts.js';
import { AnthropicLLMChatMessage, AnthropicReasoning, LLMChatMessage, LLMFIMMessage, OpenAILLMChatMessage, RawToolParamsObj } from '../common/sendLLMMessageTypes.js';
import { IVoidSettingsService } from '../common/voidSettingsService.js';
import { ChatMode, FeatureName, ModelSelection } from '../common/voidSettingsTypes.js';
import { IDirectoryStrService } from './directoryStrService.js';
import { ITerminalToolService } from './terminalToolService.js';
import { IVoidModelService } from '../common/voidModelService.js';
import { URI } from '../../../../base/common/uri.js';
@@ -143,13 +145,19 @@ const prepareMessages_anthropic_tools = (messages: SimpleLLMMessage[], supportsA
// add anthropic reasoning
if (currMsg.role === 'assistant') {
if (currMsg.anthropicReasoning && supportsAnthropicReasoning) {
const content = currMsg.content
newMessages[i] = {
role: 'assistant',
content: content ? [...currMsg.anthropicReasoning, { type: 'text' as const, text: content }] : currMsg.anthropicReasoning
}
}
else {
newMessages[i] = {
role: 'assistant',
content: currMsg.content,
// strip away anthropicReasoning
}
}
continue
}
@@ -199,7 +207,7 @@ const prepareMessages_XML_tools = (messages: SimpleLLMMessage[], supportsAnthrop
// alternatively, could just hold onto the original output, but this way requires less piping raw strings everywhere
let content: LLMChatMessage['content'] = c.content
if (next?.role === 'tool') {
content = `${content}\n\n${toolCallXMLStr(next.name, next.rawParams)}`
content = `${content}\n\n${reParsedToolXMLString(next.name, next.rawParams)}`
}
// anthropic reasoning
@@ -406,9 +414,9 @@ const prepareMessages = ({
export interface IConvertToLLMMessageService {
readonly _serviceBrand: undefined;
prepareLLMSimpleMessages: (opts: { simpleMessages: SimpleLLMMessage[], systemMessage: string, modelSelection: ModelSelection | null, featureName: FeatureName }) => { messages: LLMChatMessage[], separateSystemMessage: string | undefined };
prepareLLMSimpleMessages: (opts: { simpleMessages: SimpleLLMMessage[], systemMessage: string, modelSelection: ModelSelection | null, featureName: FeatureName }) => { messages: LLMChatMessage[], separateSystemMessage: string | undefined }
prepareLLMChatMessages: (opts: { chatMessages: ChatMessage[], chatMode: ChatMode, modelSelection: ModelSelection | null }) => Promise<{ messages: LLMChatMessage[], separateSystemMessage: string | undefined }>
prepareFIMMessage(opts: { messages: LLMFIMMessage, aiInstructions: string, }): { prefix: string, suffix: string, stopTokens: string[] }
prepareFIMMessage(opts: { messages: LLMFIMMessage, }): { prefix: string, suffix: string, stopTokens: string[] }
}
@@ -425,10 +433,35 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess
@IDirectoryStrService private readonly directoryStrService: IDirectoryStrService,
@ITerminalToolService private readonly terminalToolService: ITerminalToolService,
@IVoidSettingsService private readonly voidSettingsService: IVoidSettingsService,
@IVoidModelService private readonly voidModelService: IVoidModelService,
) {
super()
}
// Read .voidinstructions files from workspace folders
private _getVoidInstructionsFileContents(): string {
const workspaceFolders = this.workspaceContextService.getWorkspace().folders;
let voidInstructions = '';
for (const folder of workspaceFolders) {
const uri = URI.joinPath(folder.uri, '.voidinstructions')
const { model } = this.voidModelService.getModel(uri)
if (!model) continue
voidInstructions += model.getValue() + '\n\n';
}
return voidInstructions.trim();
}
// Get combined AI instructions from settings and .voidinstructions files
private _getCombinedAIInstructions(): string {
const globalAIInstructions = this.voidSettingsService.state.globalSettings.aiInstructions;
const voidInstructionsFileContent = this._getVoidInstructionsFileContents();
const ans: string[] = []
if (globalAIInstructions) ans.push(globalAIInstructions)
if (voidInstructionsFileContent) ans.push(voidInstructionsFileContent)
return ans.join('\n\n')
}
// system message
private _generateChatMessagesSystemMessage = async (chatMode: ChatMode, specialToolFormat: 'openai-style' | 'anthropic-style' | undefined) => {
@@ -442,7 +475,7 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess
`...Directories string cut off, use tools to read more...`
: `...Directories string cut off, ask user for more if necessary...`
})
const includeXMLToolDefinitions = specialToolFormat === undefined
const includeXMLToolDefinitions = !specialToolFormat
const runningTerminalIds = this.terminalToolService.listTerminalIds()
const systemMessage = chat_systemMessage({ workspaceFolders, openedURIs, directoryStr, activeURI, runningTerminalIds, chatMode, includeXMLToolDefinitions })
@@ -496,7 +529,9 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess
} = getModelCapabilities(providerName, modelName)
const modelSelectionOptions = this.voidSettingsService.state.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName]
const aiInstructions = this.voidSettingsService.state.globalSettings.aiInstructions
// Get combined AI instructions
const aiInstructions = this._getCombinedAIInstructions();
const isReasoningEnabled = getIsReasoningEnabledState(featureName, providerName, modelName, modelSelectionOptions)
const maxOutputTokens = getMaxOutputTokens(providerName, modelName, { isReasoningEnabled })
@@ -512,7 +547,6 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess
maxOutputTokens,
})
return { messages, separateSystemMessage };
}
prepareLLMChatMessages: IConvertToLLMMessageService['prepareLLMChatMessages'] = async ({ chatMessages, chatMode, modelSelection }) => {
if (modelSelection === null) return { messages: [], separateSystemMessage: undefined }
@@ -525,7 +559,9 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess
const systemMessage = await this._generateChatMessagesSystemMessage(chatMode, specialToolFormat)
const modelSelectionOptions = this.voidSettingsService.state.optionsOfModelSelection['Chat'][modelSelection.providerName]?.[modelSelection.modelName]
const aiInstructions = this.voidSettingsService.state.globalSettings.aiInstructions
// Get combined AI instructions
const aiInstructions = this._getCombinedAIInstructions();
const isReasoningEnabled = getIsReasoningEnabledState('Chat', providerName, modelName, modelSelectionOptions)
const maxOutputTokens = getMaxOutputTokens(providerName, modelName, { isReasoningEnabled })
@@ -542,19 +578,20 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess
maxOutputTokens,
})
return { messages, separateSystemMessage };
}
// --- FIM ---
prepareFIMMessage: IConvertToLLMMessageService['prepareFIMMessage'] = ({ messages, aiInstructions }) => {
prepareFIMMessage: IConvertToLLMMessageService['prepareFIMMessage'] = ({ messages }) => {
// Get combined AI instructions with the provided aiInstructions as the base
const combinedInstructions = this._getCombinedAIInstructions();
let prefix = `\
${!aiInstructions ? '' : `\
${!combinedInstructions ? '' : `\
// Instructions:
// Do not output an explanation. Try to avoid outputting comments. Only output the middle code.
${aiInstructions.split('\n').map(line => `//${line}`).join('\n')}`}
${combinedInstructions.split('\n').map(line => `//${line}`).join('\n')}`}
${messages.prefix}`
@@ -567,7 +604,6 @@ ${messages.prefix}`
}
// pick one and delete the other:
registerSingleton(IConvertToLLMMessageService, ConvertToLLMMessageService, InstantiationType.Eager);
@@ -0,0 +1,37 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import { Disposable } from '../../../../base/common/lifecycle.js';
import { URI } from '../../../../base/common/uri.js';
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js';
import { IVoidModelService } from '../common/voidModelService.js';
class ConvertContribWorkbenchContribution extends Disposable implements IWorkbenchContribution {
static readonly ID = 'workbench.contrib.void.convertcontrib'
_serviceBrand: undefined;
constructor(
@IVoidModelService private readonly voidModelService: IVoidModelService,
@IWorkspaceContextService private readonly workspaceContext: IWorkspaceContextService,
) {
super()
const initializeURI = (uri: URI) => {
this.workspaceContext.getWorkspace()
const voidInstrsURI = URI.joinPath(uri, '.voidinstructions')
this.voidModelService.initializeModel(voidInstrsURI)
}
// call
this._register(this.workspaceContext.onDidChangeWorkspaceFolders((e) => {
[...e.changed, ...e.added].forEach(w => { initializeURI(w.uri) })
}))
this.workspaceContext.getWorkspace().folders.forEach(w => { initializeURI(w.uri) })
}
}
registerWorkbenchContribution2(ConvertContribWorkbenchContribution.ID, ConvertContribWorkbenchContribution, WorkbenchPhase.BlockRestore);
@@ -258,6 +258,22 @@ class EditCodeService extends Disposable implements IEditCodeService {
this._realignAllDiffAreasLines(uri, change.text, change.range)
}
this._refreshStylesAndDiffsInURI(uri)
// if diffarea has no diffs after a user edit, delete it
const diffAreasToDelete: DiffZone[] = []
for (const diffareaid of this.diffAreasOfURI[uri.fsPath] ?? []) {
const diffArea = this.diffAreaOfId[diffareaid] ?? null
const shouldDelete = diffArea?.type === 'DiffZone' && Object.keys(diffArea._diffOfId).length === 0
if (shouldDelete) {
diffAreasToDelete.push(diffArea)
}
}
if (diffAreasToDelete.length !== 0) {
const { onFinishEdit } = this._addToHistory(uri)
diffAreasToDelete.forEach(da => this._deleteDiffZone(da))
onFinishEdit()
}
}
@@ -1562,20 +1578,22 @@ class EditCodeService extends Disposable implements IEditCodeService {
}
const errContentOfInvalidStr = (str: string & ReturnType<typeof findTextInCode>, blockOrig: string, blockNum: number, blocks: ExtractedSearchReplaceBlock[]) => {
const errContentOfInvalidStr = (str: 'Not found' | 'Not unique' | 'Has overlap', blockOrig: string) => {
const descStr = str === `Not found` ?
`The most recent ORIGINAL code could not be found in the file, so you were interrupted. The text in ORIGINAL must EXACTLY match lines of code. The problematic ORIGINAL code was:\n${JSON.stringify(blockOrig)}`
: str === `Not unique` ?
`The most recent ORIGINAL code shows up multiple times in the file, so you were interrupted. You might want to expand the ORIGINAL excerpt so it's unique. The problematic ORIGINAL code was:\n${JSON.stringify(blockOrig)}`
: ``
: str === 'Has overlap' ?
`The most recent ORIGINAL code has overlap with another ORIGINAL code block that you outputted. Do NOT output any overlapping edits. The problematic ORIGINAL code was:\n${JSON.stringify(blockOrig)}`
: ``
// string of <<<<< ORIGINAL >>>>> REPLACE blocks so far so LLM can understand what it currently has
// const blocksSoFarStr = blocks.slice(0, blockNum).map(block => `${ORIGINAL}\n${block.orig}\n${DIVIDER}\n${block.final}\n${FINAL}`).join('\n')
// const soFarStr = blocksSoFarStr ? `These are the Search/Replace blocks that have been applied so far:${tripleTick[0]}\n${blocksSoFarStr}\n${tripleTick[1]}` : ''
// const continueMsg = soFarStr ? `${soFarStr}Please continue outputting SEARCH/REPLACE blocks starting where this leaves off.` : ''
// const errMsg = `${descStr}${continueMsg ? `\n${continueMsg}` : ''}`
const soFarStr = 'All of your previous outputs have been ignored. Please re-output ALL SEARCH/REPLACE blocks starting from the first one, and avoid the error.'
const soFarStr = 'All of your previous outputs have been ignored. Please re-output ALL SEARCH/REPLACE blocks starting from the first one, and avoid the error this time.'
const errMsg = `${descStr}\n${soFarStr}`
return errMsg
@@ -1610,7 +1628,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
const addedTrackingZoneOfBlockNum: TrackingZone<SearchReplaceDiffAreaMetadata>[] = []
diffZone._streamState.line = 1
const N_RETRIES = 2
const N_RETRIES = 4
// allowed to throw errors - this is called inside a promise that handles everything
const runSearchReplace = async () => {
@@ -1684,17 +1702,25 @@ class EditCodeService extends Disposable implements IEditCodeService {
// if this is the first time we're seeing this block, add it as a diffarea so we can start streaming in it
if (!(blockNum in addedTrackingZoneOfBlockNum)) {
const originalBounds = findTextInCode(block.orig, originalFileCode, true)
// if error
if (typeof originalBounds === 'string') {
// Check for overlap with existing modified ranges
const hasOverlap = addedTrackingZoneOfBlockNum.some(trackingZone => {
const [existingStart, existingEnd] = trackingZone.metadata.originalBounds;
const hasNoOverlap = endLine < existingStart || startLine > existingEnd
return !hasNoOverlap
});
if (typeof originalBounds === 'string' || hasOverlap) {
const errorMessage = typeof originalBounds === 'string' ? originalBounds : 'Has overlap' as const
console.log('--------------Error finding text in code:')
console.log('originalFileCode', { originalFileCode })
console.log('fullText', { fullText })
console.log('error:', originalBounds)
console.log('error:', errorMessage)
console.log('block.orig:', block.orig)
console.log('---------')
const content = errContentOfInvalidStr(originalBounds, block.orig, blockNum, blocks)
const content = errContentOfInvalidStr(errorMessage, block.orig)
messages.push(
{ role: 'assistant', content: fullText }, // latest output
{ role: 'user', content: content } // user explanation of what's wrong
@@ -3,7 +3,7 @@
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
.monaco-editor .void-sweepIdxBG {
.monaco-editor .void-sweepIdxBG {
background-color: var(--vscode-void-sweepIdxBG);
}
@@ -23,10 +23,30 @@
background-color: var(--vscode-void-redBG);
}
.void-watermark-button {
margin: 8px 0;
/* Renamed from void-watermark-button to void-openfolder-button */
.void-openfolder-button {
padding: 8px 20px;
background-color: #3b82f6;
background-color: #306dce;
color: white;
border: none;
border-radius: 4px;
outline: none !important;
box-shadow: none !important;
cursor: pointer;
transition: background-color 0.2s ease;
}
.void-openfolder-button:hover {
background-color: #2563eb;
}
.void-openfolder-button:active {
background-color: #2563eb;
}
/* Added for Open SSH button with slightly darker color */
.void-openssh-button {
padding: 8px 20px;
background-color: #656565; /* Slightly darker than the #5a5a5a in the TS file */
color: white;
border: none;
border-radius: 4px;
@@ -35,43 +55,41 @@
cursor: pointer;
transition: background-color 0.2s ease;
}
.void-watermark-button:hover {
background-color: #2563eb;
.void-openssh-button:hover {
background-color: #474747; /* Darker on hover */
}
.void-watermark-button:active {
background-color: #2563eb;
.void-openssh-button:active {
background-color: #474747;
}
.void-settings-watermark-button {
margin: 8px 0;
padding: 8px 20px;
background-color: var(--vscode-input-background);
margin: 8px 0;
padding: 8px 20px;
background-color: var(--vscode-input-background);
color: var(--vscode-input-foreground);
border: none;
border-radius: 4px;
outline: none !important;
border: none;
border-radius: 4px;
outline: none !important;
box-shadow: none !important;
cursor: pointer;
transition: all 0.2s ease;
cursor: pointer;
transition: all 0.2s ease;
}
.void-settings-watermark-button:hover {
filter: brightness(1.1);
}
.void-settings-watermark-button:active {
filter: brightness(1.1);
}
.void-link {
color: #3b82f6;
cursor: pointer;
transition: all 0.2s ease;
}
.void-link:hover {
opacity: 80%;
}
@@ -86,7 +104,8 @@
.void-scope,
.void-scope * {
scrollbar-width: thin !important;
scrollbar-color: var(--void-bg-1) var(--void-bg-3) !important; /* For Firefox */
scrollbar-color: var(--void-bg-1) var(--void-bg-3) !important;
/* For Firefox */
}
.void-scope::-webkit-scrollbar,
@@ -133,13 +152,16 @@
background-color: var(--vscode-editor-background);
--scrollbar-vertical-width: 14px;
--scrollbar-horizontal-height: 6px;
overflow: auto; /* Ensure scrollbars are shown when needed */
overflow: auto;
/* Ensure scrollbars are shown when needed */
}
.void-scrollable-element,
.void-scrollable-element * {
scrollbar-width: thin !important; /* For Firefox */
scrollbar-color: var(--void-bg-1) var(--void-bg-3) !important; /* For Firefox */
scrollbar-width: thin !important;
/* For Firefox */
scrollbar-color: var(--void-bg-1) var(--void-bg-3) !important;
/* For Firefox */
}
.void-scrollable-element::-webkit-scrollbar,
@@ -69,7 +69,7 @@ export const IconShell1 = ({ onClick, Icon, disabled, className, ...props }: Ico
const COPY_FEEDBACK_TIMEOUT = 1500 // amount of time to say 'Copied!'
export const CopyButton = ({ codeStr }: { codeStr: string }) => {
export const CopyButton = ({ codeStr, toolTipName }: { codeStr: string | (() => Promise<string> | string), toolTipName: string }) => {
const accessor = useAccessor()
const metricsService = accessor.get('IMetricsService')
@@ -83,8 +83,8 @@ export const CopyButton = ({ codeStr }: { codeStr: string }) => {
}, COPY_FEEDBACK_TIMEOUT)
}, [copyButtonText])
const onCopy = useCallback(() => {
clipboardService.writeText(codeStr)
const onCopy = useCallback(async () => {
clipboardService.writeText(typeof codeStr === 'string' ? codeStr : await codeStr())
.then(() => { setCopyButtonText(CopyButtonText.Copied) })
.catch(() => { setCopyButtonText(CopyButtonText.Error) })
metricsService.capture('Copy Code', { length: codeStr.length }) // capture the length only
@@ -93,7 +93,7 @@ export const CopyButton = ({ codeStr }: { codeStr: string }) => {
return <IconShell1
Icon={copyButtonText === CopyButtonText.Copied ? Check : copyButtonText === CopyButtonText.Error ? X : Copy}
onClick={onCopy}
{...tooltipPropsForApplyBlock({ tooltipName: 'Copy' })}
{...tooltipPropsForApplyBlock({ tooltipName: toolTipName })}
/>
}
@@ -374,7 +374,7 @@ export const BlockCodeApplyWrapper = ({
</div>
<div className={`${canApply ? '' : 'hidden'} flex items-center gap-1`}>
<JumpToFileButton uri={uri} />
{currStreamState === 'idle-no-changes' && <CopyButton codeStr={initValue} />}
{currStreamState === 'idle-no-changes' && <CopyButton codeStr={initValue} toolTipName='Copy' />}
<ApplyButtonsHTML uri={uri} applyBoxId={applyBoxId} codeStr={initValue} reapplyIcon={false} />
</div>
</div>
@@ -5,6 +5,7 @@
import React, { Component, ErrorInfo, ReactNode } from 'react';
import { ErrorDisplay } from './ErrorDisplay.js';
import { WarningBox } from '../void-settings-tsx/WarningBox.js';
interface Props {
children: ReactNode;
@@ -51,11 +52,12 @@ class ErrorBoundary extends Component<Props, State> {
// Use ErrorDisplay component as the default error UI
return (
<ErrorDisplay
message={this.state.error + ''}
fullError={this.state.error}
onDismiss={this.props.onDismiss || null}
/>
<WarningBox text={this.state.error + ''} />
// <ErrorDisplay
// message={this.state.error + ''}
// fullError={this.state.error}
// onDismiss={this.props.onDismiss || null}
// />
);
}
@@ -17,10 +17,10 @@ import { ModelDropdown, } from '../void-settings-tsx/ModelDropdown.js';
import { SidebarThreadSelector } from './SidebarThreadSelector.js';
import { VOID_CTRL_L_ACTION_ID } from '../../../actionIDs.js';
import { VOID_OPEN_SETTINGS_ACTION_ID } from '../../../voidSettingsPane.js';
import { ChatMode, FeatureName, isFeatureNameDisabled } from '../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js';
import { ChatMode, displayInfoOfProviderName, FeatureName, isFeatureNameDisabled } from '../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js';
import { WarningBox } from '../void-settings-tsx/WarningBox.js';
import { getModelCapabilities, getIsReasoningEnabledState } from '../../../../common/modelCapabilities.js';
import { AlertTriangle, Ban, Check, ChevronRight, Dot, FileIcon, Pencil, Undo, Undo2, X, Flag } from 'lucide-react';
import { AlertTriangle, Ban, Check, ChevronRight, Dot, FileIcon, Pencil, Undo, Undo2, X, Flag, Copy as CopyIcon } from 'lucide-react';
import { ChatMessage, CheckpointEntry, StagingSelectionItem, ToolMessage } from '../../../../common/chatThreadServiceTypes.js';
import { LintErrorItem, ToolCallParams, ToolNameWithApproval } from '../../../../common/toolsServiceTypes.js';
import { ApplyButtonsHTML, CopyButton, IconShell1, JumpToFileButton, JumpToTerminalButton, StatusIndicator, StatusIndicatorForApplyButton, useApplyButtonState } from '../markdown/ApplyBlockHoverButtons.js';
@@ -29,6 +29,7 @@ import { acceptAllBg, acceptBorder, buttonFontSize, buttonTextColor, rejectAllBg
import { ToolName, toolNames } from '../../../../common/prompt/prompts.js';
import { RawToolCallObj } from '../../../../common/sendLLMMessageTypes.js';
import { MAX_FILE_CHARS_PAGE } from '../../../toolsService.js';
import ErrorBoundary from './ErrorBoundary.js';
@@ -1126,16 +1127,18 @@ const AssistantMessageComponent = ({ chatMessage, isCheckpointGhost, isCommitted
}
{/* assistant message */}
<div className={`${isCheckpointGhost ? 'opacity-50' : ''}`}>
<ProseWrapper>
<ChatMarkdownRender
string={chatMessage.displayContent || ''}
chatMessageLocation={chatMessageLocation}
isApplyEnabled={true}
isLinkDetectionEnabled={true}
/>
</ProseWrapper>
</div>
{chatMessage.displayContent &&
<div className={`${isCheckpointGhost ? 'opacity-50' : ''}`}>
<ProseWrapper>
<ChatMarkdownRender
string={chatMessage.displayContent || ''}
chatMessageLocation={chatMessageLocation}
isApplyEnabled={true}
isLinkDetectionEnabled={true}
/>
</ProseWrapper>
</div>
}
</>
}
@@ -1176,7 +1179,7 @@ const titleOfToolName = {
'create_file_or_folder': { done: `Created`, proposed: `Create`, running: loadingTitleWrapper(`Creating`) },
'delete_file_or_folder': { done: `Deleted`, proposed: `Delete`, running: loadingTitleWrapper(`Deleting`) },
'edit_file': { done: `Edited file`, proposed: 'Edit file', running: loadingTitleWrapper('Editing file') },
'run_terminal_command': { done: `Ran terminal`, proposed: 'Run terminal', running: loadingTitleWrapper('Running terminal') },
'command_tool': { done: `Ran terminal`, proposed: 'Run terminal', running: loadingTitleWrapper('Running terminal') },
'read_lint_errors': { done: `Read lint errors`, proposed: 'Read lint errors', running: loadingTitleWrapper('Reading lint errors') },
} as const satisfies Record<ToolName, { done: any, proposed: any, running: any }>
@@ -1218,8 +1221,8 @@ const toolNameToDesc = (toolName: ToolName, _toolParams: ToolCallParams[ToolName
} else if (toolName === 'edit_file') {
const toolParams = _toolParams as ToolCallParams['edit_file']
return getBasename(toolParams.uri.fsPath);
} else if (toolName === 'run_terminal_command') {
const toolParams = _toolParams as ToolCallParams['run_terminal_command']
} else if (toolName === 'command_tool') {
const toolParams = _toolParams as ToolCallParams['command_tool']
return `"${toolParams.command}"`;
} else {
return ''
@@ -1364,12 +1367,12 @@ const EditToolLintErrors = ({ lintErrors }: { lintErrors: LintErrorItem[] }) =>
style={{ background: 'none' }}
>
<ChevronRight
className={`mr-1 h-4 w-4 flex-shrink-0 transition-transform duration-100 text-void-fg-4 group-hover:text-void-fg-3 ${isOpen ? 'rotate-90' : ''}`}
className={`mr-1 h-3 w-3 flex-shrink-0 transition-transform duration-100 text-void-fg-4 group-hover:text-void-fg-3 ${isOpen ? 'rotate-90' : ''}`}
/>
<span className="font-medium text-void-fg-4 group-hover:text-void-fg-3 text-xs">Lint errors</span>
</div>
<div
className={`overflow-hidden transition-all duration-200 ease-in-out ${isOpen ? 'opacity-100 py-1' : 'max-h-0 opacity-0'} text-xs pl-5`}
className={`overflow-hidden transition-all duration-200 ease-in-out ${isOpen ? 'opacity-100' : 'max-h-0 opacity-0'} text-xs pl-4`}
>
<div className="flex flex-col gap-0.5 overflow-x-auto whitespace-nowrap text-void-fg-4 opacity-90 border-l-2 border-void-warning px-2 py-0.5">
{lintErrors.map((error, i) => (
@@ -1389,7 +1392,7 @@ const EditToolHeaderButtons = ({ applyBoxId, uri, codeStr }: { applyBoxId: strin
<StatusIndicatorForApplyButton applyBoxId={applyBoxId} uri={uri} />
<JumpToFileButton uri={uri} />
{currStreamState === 'idle-no-changes' && <CopyButton codeStr={codeStr} />}
{currStreamState === 'idle-no-changes' && <CopyButton codeStr={codeStr} toolTipName='Copy' />}
<ApplyButtonsHTML applyBoxId={applyBoxId} uri={uri} codeStr={codeStr} reapplyIcon={true} />
</div>
}
@@ -1577,11 +1580,12 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
const componentParams: ToolHeaderParams = { title, desc1, isError, icon }
if (toolMessage.type === 'success') {
const { params, result } = toolMessage
const { params, result, rawParams } = toolMessage
componentParams.numResults = result.uris.length
componentParams.hasNextPage = result.hasNextPage
componentParams.children = result.uris.length === 0 ? undefined
: <ToolChildrenWrapper>
{rawParams.search_in_folder ? `Search in ${rawParams.search_in_folder}` : null}
{result.uris.map((uri, i) => (<ListableToolItem key={i}
name={getBasename(uri.fsPath)}
className='w-full overflow-auto'
@@ -1621,11 +1625,12 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
const componentParams: ToolHeaderParams = { title, desc1, isError, icon }
if (toolMessage.type === 'success') {
const { params, result } = toolMessage
const { params, result, rawParams } = toolMessage
componentParams.numResults = result.uris.length
componentParams.hasNextPage = result.hasNextPage
componentParams.children = result.uris.length === 0 ? undefined
: <ToolChildrenWrapper>
{rawParams.search_in_folder ? `Search in ${rawParams.search_in_folder}` : null}
{result.uris.map((uri, i) => (<ListableToolItem key={i}
name={getBasename(uri.fsPath)}
className='w-full overflow-auto'
@@ -1855,7 +1860,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
return <ToolHeaderWrapper {...componentParams} />
}
},
'run_terminal_command': {
'command_tool': {
resultWrapper: ({ toolMessage }) => {
const accessor = useAccessor()
const commandService = accessor.get('ICommandService')
@@ -1873,9 +1878,10 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
const { command } = params
const { terminalId, resolveReason, result: terminalResult } = result
componentParams.desc2 = <JumpToTerminalButton
onClick={() => { terminalToolsService.openTerminal(terminalId) }}
/>
// it's unclear that this is a button and not an icon.
// componentParams.desc2 = <JumpToTerminalButton
// onClick={() => { terminalToolsService.openTerminal(terminalId) }}
// />
const additionalDetailsStr = resolveReason.type === 'done' ? (resolveReason.exitCode !== 0 ? `\nError: exit code ${resolveReason.exitCode}` : null)
: resolveReason.type === 'bgtask' ? null :
@@ -1890,12 +1896,12 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
<span className="text-void-fg-1 font-sans">{`Ran command: `}</span>
<span className="font-mono">{command}</span>
</div>
<div>
{(terminalResult + additionalDetailsStr).length && <div>
<span>{resolveReason.type === 'bgtask' ? 'Result so far:\n' : null}</span>
<span>{`Result: `}</span>
<span className="text-void-fg-1 font-mono">{terminalResult}</span>
<span className="text-void-fg-1 font-mono">{additionalDetailsStr}</span>
</div>
<span className='text-void-fg-1'>{`Result: `}</span>
<span className="font-mono">{terminalResult}</span>
<span className="font-mono">{additionalDetailsStr}</span>
</div>}
</div>
</ToolChildrenWrapper>
@@ -1949,10 +1955,14 @@ const Checkpoint = ({ message, threadId, messageIdx, isCheckpointGhost, threadIs
style={{ position: 'relative', display: 'inline-block' }} // allow absolute icon
onClick={() => {
if (threadIsRunning) return
chatThreadService.jumpToCheckpointBeforeMessageIdx({ threadId, messageIdx, jumpToUserModified: true })
chatThreadService.jumpToCheckpointBeforeMessageIdx({
threadId,
messageIdx,
jumpToUserModified: messageIdx === (chatThreadService.state.allThreads[threadId]?.messages.length ?? 0) - 1
})
}}
>
Checkpoint
Checkpoint
</div>
</div>
}
@@ -2116,9 +2126,49 @@ const CommandBarInChat = () => {
const accessor = useAccessor()
const editCodeService = accessor.get('IEditCodeService')
const commandService = accessor.get('ICommandService')
const chatThreadsService = accessor.get('IChatThreadService')
const chatThreadsState = useChatThreadsState()
const commandBarState = useCommandBarState()
const chatThreadsStreamState = useChatThreadsStreamState(chatThreadsState.currentThreadId)
const settingsState = useSettingsState()
const convertService = accessor.get('IConvertToLLMMessageService')
const currentThread = chatThreadsService.getCurrentThread()
const chatMode = settingsState.globalSettings.chatMode
const modelSelection = settingsState.modelSelectionOfFeature?.Chat ?? null
const copyChatButton = <CopyButton
codeStr={async () => {
const { messages } = await convertService.prepareLLMChatMessages({
chatMessages: currentThread.messages,
chatMode,
modelSelection,
})
return JSON.stringify(messages, null, 2)
}}
toolTipName={modelSelection === null ? 'Copy As Messages Payload' : `Copy As ${displayInfoOfProviderName(modelSelection.providerName).title} Payload`}
/>
const copyChatButton2 = <CopyButton
codeStr={async () => {
return JSON.stringify(currentThread.messages, null, 2)
}}
toolTipName={`Copy As Void Chat`}
/>
// (
// <IconShell1
// Icon={CopyIcon}
// onClick={copyChatToClipboard}
// data-tooltip-id='void-tooltip'
// data-tooltip-place='top'
// data-tooltip-content='Copy chat JSON'
// />
// )
const [fileDetailsOpenedState, setFileDetailsOpenedState] = useState<'auto-opened' | 'auto-closed' | 'user-opened' | 'user-closed'>('auto-closed');
const isFileDetailsOpened = fileDetailsOpenedState === 'auto-opened' || fileDetailsOpenedState === 'user-opened';
@@ -2136,7 +2186,12 @@ const CommandBarInChat = () => {
}, [fileDetailsOpenedState, setFileDetailsOpenedState, numFilesChanged])
const isFinishedMakingThreadChanges = numFilesChanged !== 0 && (chatThreadsStreamState ? !chatThreadsStreamState.isRunning : true)
const isFinishedMakingThreadChanges = (
// there are changed files
commandBarState.sortedURIs.length !== 0
// none of the files are streaming
&& commandBarState.sortedURIs.every(uri => !commandBarState.stateOfURI[uri.fsPath]?.isStreaming)
)
// ======== status of agent ========
// This icon answers the question "is the LLM doing work on this thread?"
@@ -2479,10 +2534,10 @@ export const SidebarChat = () => {
const currCheckpointIdx = chatThreadsState.allThreads[threadId]?.state?.currCheckpointIdx ?? undefined // if not exist, treat like checkpoint is last message (infinity)
const previousMessagesHTML = useMemo(() => {
const lastMessageIdx = previousMessages.findLastIndex(v => v.role !== 'checkpoint')
// const lastMessageIdx = previousMessages.findLastIndex(v => v.role !== 'checkpoint')
// tool request shows up as Editing... if in progress
return previousMessages.map((message, i) => {
return <ChatBubble
return <ErrorBoundary><ChatBubble
key={getChatBubbleId(threadId, i)}
currCheckpointIdx={currCheckpointIdx}
chatMessage={message}
@@ -2491,13 +2546,13 @@ export const SidebarChat = () => {
chatIsRunning={isRunning}
threadId={threadId}
_scrollToBottom={() => scrollToBottom(scrollContainerRef)}
/>
/></ErrorBoundary>
})
}, [previousMessages, threadId, currCheckpointIdx, isRunning])
const streamingChatIdx = previousMessagesHTML.length
const currStreamingMessageHTML = reasoningSoFar || displayContentSoFar || isRunning ?
<ChatBubble
<ErrorBoundary><ChatBubble
key={getChatBubbleId(threadId, streamingChatIdx)}
currCheckpointIdx={currCheckpointIdx}
chatMessage={{
@@ -2512,7 +2567,7 @@ export const SidebarChat = () => {
threadId={threadId}
_scrollToBottom={null}
/> : null
/></ErrorBoundary> : null
// the tool currently being generated
@@ -2616,14 +2671,20 @@ export const SidebarChat = () => {
<div ref={sidebarRef} className='w-full h-full flex flex-col overflow-hidden'>
{/* History selector */}
<div className={`w-full ${isHistoryOpen ? '' : 'hidden'} ring-2 ring-widget-shadow ring-inset z-10`}>
<SidebarThreadSelector />
<ErrorBoundary>
<SidebarThreadSelector />
</ErrorBoundary>
</div>
<div className='flex-1 flex flex-col overflow-hidden'>
<div className={`flex-1 overflow-hidden ${previousMessages.length === 0 ? 'h-0 max-h-0 pb-2' : ''}`}>
{messagesHTML}
<ErrorBoundary>
{messagesHTML}
</ErrorBoundary>
</div>
{inputForm}
<ErrorBoundary>
{inputForm}
</ErrorBoundary>
</div>
</div>
)
@@ -11,7 +11,8 @@
--void-bg-1: var(--vscode-input-background);
--void-bg-1-alt: var(--vscode-badge-background);
--void-bg-2: var(--vscode-sideBar-background);
--void-bg-2-alt: color-mix(in srgb, var(--vscode-sideBar-background) 30%, var(--vscode-editor-background) 70%);
--void-bg-2-alt: color-mix(in srgb, var(--vscode-editor-background) 30%, var(--vscode-sideBar-background) 70%);
--void-bg-2-hover: color-mix(in srgb, var(--vscode-editor-foreground) 5%, var(--vscode-sideBar-background) 95%);
--void-bg-3: var(--vscode-editor-background);
--void-fg-0: color-mix(in srgb, var(--vscode-tab-activeForeground) 90%, black 10%);
@@ -664,57 +664,60 @@ export const VoidCustomDropdownBox = <T extends NonNullable<any>>({
{isOpen && (
<div
ref={refs.setFloating}
className="z-10 bg-void-bg-1 border-void-border-1 border overflow-hidden rounded shadow-lg"
className="z-10 bg-void-bg-1 border-void-border-3 border rounded shadow-lg"
style={{
position: strategy,
top: y ?? 0,
left: x ?? 0,
width: matchInputWidth
width: (matchInputWidth
? (refs.reference.current instanceof HTMLElement ? refs.reference.current.offsetWidth : 0)
: Math.max(
(refs.reference.current instanceof HTMLElement ? refs.reference.current.offsetWidth : 0),
(measureRef.current instanceof HTMLElement ? measureRef.current.offsetWidth : 0)
),
))
}}
>
{options.map((option) => {
const thisOptionIsSelected = getOptionsEqual(option, selectedOption);
const optionName = getOptionDropdownName(option);
const optionDetail = getOptionDropdownDetail?.(option) || '';
onWheel={(e) => e.stopPropagation()}
><div className='overflow-auto max-h-80'>
return (
<div
key={optionName}
className={`flex items-center px-2 py-1 cursor-pointer whitespace-nowrap
{options.map((option) => {
const thisOptionIsSelected = getOptionsEqual(option, selectedOption);
const optionName = getOptionDropdownName(option);
const optionDetail = getOptionDropdownDetail?.(option) || '';
return (
<div
key={optionName}
className={`flex items-center px-2 py-1 pr-4 cursor-pointer whitespace-nowrap
transition-all duration-100
bg-void-bg-1
${thisOptionIsSelected ? 'bg-void-bg-2' : 'hover:bg-void-bg-2'}
${thisOptionIsSelected ? 'bg-void-bg-2-hover' : 'bg-void-bg-2 hover:bg-void-bg-2-hover'}
`}
onClick={() => {
onChangeOption(option);
setIsOpen(false);
}}
>
<div className="w-4 flex justify-center flex-shrink-0">
{thisOptionIsSelected && (
<svg className="size-3" viewBox="0 0 12 12" fill="none">
<path
d="M10 3L4.5 8.5L2 6"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)}
onClick={() => {
onChangeOption(option);
setIsOpen(false);
}}
>
<div className="w-4 flex justify-center flex-shrink-0">
{thisOptionIsSelected && (
<svg className="size-3" viewBox="0 0 12 12" fill="none">
<path
d="M10 3L4.5 8.5L2 6"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)}
</div>
<span className="flex justify-between w-full">
<span>{optionName}</span>
<span className='text-void-fg-4 opacity-60'>{optionDetail}</span>
</span>
</div>
<span className="flex justify-between w-full">
<span>{optionName}</span>
<span className='text-void-fg-4 opacity-60'>{optionDetail}</span>
</span>
</div>
);
})}
);
})}
</div>
</div>
)}
</div>
@@ -48,6 +48,7 @@ import { IVoidCommandBarService } from '../../../voidCommandBarService.js'
import { INativeHostService } from '../../../../../../../platform/native/common/native.js';
import { IEditCodeService } from '../../../editCodeServiceInterface.js'
import { IToolsService } from '../../../toolsService.js'
import { IConvertToLLMMessageService } from '../../../convertToLLMMessageService.js'
// normally to do this you'd use a useEffect that calls .onDidChangeState(), but useEffect mounts too late and misses initial state changes
@@ -217,6 +218,7 @@ const getReactAccessor = (accessor: ServicesAccessor) => {
IVoidCommandBarService: accessor.get(IVoidCommandBarService),
INativeHostService: accessor.get(INativeHostService),
IToolsService: accessor.get(IToolsService),
IConvertToLLMMessageService: accessor.get(IConvertToLLMMessageService),
} as const
return reactAccessor
@@ -111,7 +111,7 @@ const VoidCommandBar = ({ uri, editor }: VoidCommandBarProps) => {
const { model } = await voidModelService.getModelSafe(nextURI)
if (model) {
// switch to the URI
editorService.openCodeEditor({ resource: nextURI, options: { revealIfVisible: true } }, editor)
editorService.openCodeEditor({ resource: model.uri, options: { revealIfVisible: true } }, editor)
}
}
@@ -3,15 +3,16 @@
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import { useEffect, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import { useAccessor, useIsDark, useSettingsState } from '../util/services.js';
import { Check, ExternalLink, X } from 'lucide-react';
import { Brain, Check, ChevronRight, DollarSign, ExternalLink, Lock, X } from 'lucide-react';
import { displayInfoOfProviderName, ProviderName, providerNames, refreshableProviderNames } from '../../../../common/voidSettingsTypes.js';
import { getModelCapabilities, ollamaRecommendedModels } from '../../../../common/modelCapabilities.js';
import { ChatMarkdownRender } from '../markdown/ChatMarkdownRender.js';
import { AddModelInputBox, AnimatedCheckmarkButton, ollamaSetupInstructions, OneClickSwitchButton, SettingsForProvider } from '../void-settings-tsx/Settings.js';
import { ColorScheme } from '../../../../../../../platform/theme/common/theme.js';
const OVERRIDE_VALUE = true
const OVERRIDE_VALUE = false
export const VoidOnboarding = () => {
@@ -24,7 +25,6 @@ export const VoidOnboarding = () => {
<div className={`@@void-scope ${isDark ? 'dark' : ''}`}>
<div
className={`
hidden
bg-void-bg-3 fixed top-0 right-0 bottom-0 left-0 width-full h-full z-[99999]
transition-all duration-1000 ${isOnboardingComplete ? 'opacity-0 pointer-events-none' : 'opacity-100 pointer-events-auto'}
`}
@@ -35,14 +35,39 @@ export const VoidOnboarding = () => {
)
}
const VoidIcon = () => {
const accessor = useAccessor()
const themeService = accessor.get('IThemeService')
const divRef = useRef<HTMLDivElement | null>(null)
useEffect(() => {
// void icon style
const updateTheme = () => {
const theme = themeService.getColorTheme().type
const isDark = theme === ColorScheme.DARK || theme === ColorScheme.HIGH_CONTRAST_DARK
if (divRef.current) {
divRef.current.style.maxWidth = '220px'
divRef.current.style.opacity = '50%'
divRef.current.style.filter = isDark ? '' : 'invert(1)' //brightness(.5)
}
}
updateTheme()
const d = themeService.onDidColorThemeChange(updateTheme)
return () => d.dispose()
}, [])
return <div ref={divRef} className='@@void-void-icon' />
}
const FADE_DURATION_MS = 2000
const FadeIn = ({ children, className, delayMs = 0, ...props }: { children: React.ReactNode, delayMs?: number, className?: string } & React.HTMLAttributes<HTMLDivElement>) => {
const FadeIn = ({ children, className, delayMs = 0, durationMs, ...props }: { children: React.ReactNode, delayMs?: number, durationMs?: number, className?: string } & React.HTMLAttributes<HTMLDivElement>) => {
const [opacity, setOpacity] = useState(0)
const effectiveDurationMs = durationMs ?? FADE_DURATION_MS
useEffect(() => {
const timeout = setTimeout(() => {
@@ -54,7 +79,7 @@ const FadeIn = ({ children, className, delayMs = 0, ...props }: { children: Reac
return (
<div className={className} style={{ opacity, transition: `opacity ${FADE_DURATION_MS}ms ease-in-out` }} {...props}>
<div className={className} style={{ opacity, transition: `opacity ${effectiveDurationMs}ms ease-in-out` }} {...props}>
{children}
</div>
)
@@ -102,15 +127,14 @@ const FadeIn = ({ children, className, delayMs = 0, ...props }: { children: Reac
// content
// prev/next
const NextButton = ({ onClick, ...props }: { onClick: () => void } & React.ButtonHTMLAttributes<HTMLButtonElement>) => {
return (
<button
onClick={onClick}
className="px-6 py-2 bg-[#0e70c0] enabled:hover:bg-[#1177cb] disabled:opacity-50 disabled:cursor-not-allowed rounded text-white duration-300 transition-all"
className="px-6 py-2 bg-zinc-100 enabled:hover:bg-zinc-100 disabled:bg-zinc-100/40 disabled:cursor-not-allowed rounded text-black duration-600 transition-all"
{...props.disabled && {
'data-tooltip-id': 'void-tooltip',
'data-tooltip-content': 'Disabled (Please enter all required fields or choose another Provider)',
'data-tooltip-content': 'Please enter all required fields or choose another provider',
'data-tooltip-place': 'top',
}}
{...props}
@@ -120,23 +144,11 @@ const NextButton = ({ onClick, ...props }: { onClick: () => void } & React.Butto
)
}
const SkipButton = ({ onClick, ...props }: { onClick: () => void } & React.ButtonHTMLAttributes<HTMLButtonElement>) => {
return (
<button
onClick={onClick}
className="px-6 py-2 rounded bg-void-bg-2 hover:bg-void-bg-3 text-void-fg-2 duration-300 transition-all"
{...props}
>
Skip
</button>
)
}
const PreviousButton = ({ onClick, ...props }: { onClick: () => void } & React.ButtonHTMLAttributes<HTMLButtonElement>) => {
return (
<button
onClick={onClick}
className="px-6 py-2 rounded text-void-fg-3 opacity-80 hover:brightness-110 duration-300 transition-all"
className="px-6 py-2 rounded text-void-fg-3 opacity-80 hover:brightness-115 duration-600 transition-all"
{...props}
>
Back
@@ -154,10 +166,10 @@ const OnboardingPageShell = ({ top, bottom, content, hasMaxWidth = true, classNa
className?: string,
}) => {
return (
<div className={`min-h-full flex flex-col gap-4 w-full mx-auto ${hasMaxWidth ? 'max-w-[600px]' : ''} ${className}`}>
<FadeIn className='w-full pt-16'>{top}</FadeIn>
<FadeIn className='w-full my-auto'>{content}</FadeIn>
<div className='w-full pb-8'>{bottom}</div>
<div className={`min-h-full text-lg flex flex-col gap-4 w-full mx-auto ${hasMaxWidth ? 'max-w-[600px]' : ''} ${className}`}>
{top && <FadeIn className='w-full mb-auto pt-16'>{top}</FadeIn>}
{content && <FadeIn className='w-full my-auto'>{content}</FadeIn>}
{bottom && <div className='w-full pb-8'>{bottom}</div>}
</div>
)
}
@@ -170,7 +182,7 @@ const OllamaDownloadOrRemoveModelButton = ({ modelName, isModelInstalled, sizeGb
href={`https://ollama.com/library/${modelName}`}
target="_blank"
rel="noopener noreferrer"
className="flex items-center text-void-fg-2 hover:text-void-fg-1"
className="flex items-center justify-center text-void-fg-2 hover:text-void-fg-1"
>
<ExternalLink className="w-3.5 h-3.5" />
</a>
@@ -252,9 +264,9 @@ const YesNoText = ({ val }: { val: boolean | null }) => {
return <div
className={
val === true ? "text text-green-500"
: val === false ? 'text-red-500'
: "text text-yellow-500"
val === true ? "text text-emerald-500"
: val === false ? 'text-rose-600'
: "text text-amber-300"
}
>
{
@@ -344,7 +356,6 @@ const TableOfModelsForProvider = ({ providerName }: { providerName: ProviderName
} = capabilities
// TODO update this when tools work
const supportsTools = !!!((capabilities as unknown as any).supportsTools)
const removeModelButton = <button
className="absolute -left-1 top-1/2 transform -translate-y-1/2 -translate-x-full text-void-fg-3 hover:text-void-fg-1 text-xs"
@@ -364,10 +375,10 @@ const TableOfModelsForProvider = ({ providerName }: { providerName: ProviderName
<td className="py-2 px-3">${cost.output ?? ''}</td>
<td className="py-2 px-3">{contextWindow ? abbreviateNumber(contextWindow) : ''}</td>
<td className="py-2 px-3"><YesNoText val={true} /></td>
<td className="py-2 px-3"><YesNoText val={!!supportsTools} /></td>
<td className="py-2 px-3"><YesNoText val={!!true} /></td>
<td className="py-2 px-3"><YesNoText val={!!supportsFIM} /></td>
{/* <td className="py-2 px-3"><YesNoText val={!!reasoningCapabilities} /></td> */}
{isDetectableLocally && <td className="py-2 px-3">{!!isDownloaded ? <Check className="w-4 h-4" /> : <></>}</td>}
{isDetectableLocally && <td className="py-2 px-3 flex items-center justify-center">{!!isDownloaded ? <Check className="w-4 h-4" /> : <></>}</td>}
{providerName === 'ollama' && <th className="py-2 px-3">
<OllamaDownloadOrRemoveModelButton modelName={modelName} isModelInstalled={infoOfModelName[modelName].isDownloaded} sizeGb={downloadable && downloadable.sizeGb} />
</th>}
@@ -390,6 +401,57 @@ const TableOfModelsForProvider = ({ providerName }: { providerName: ProviderName
const PrimaryActionButton = ({ children, className, ringSize, ...props }: { children: React.ReactNode, ringSize?: undefined | 'xl' | 'screen' } & React.ButtonHTMLAttributes<HTMLButtonElement>) => {
return (
<button
type='button'
className={`
flex items-center justify-center
text-white dark:text-black
bg-black/90 dark:bg-white/90
${ringSize === 'xl' ? `
gap-2 px-16 py-8
hover:ring-8 active:ring-8
transition-all duration-300 ease-in-out
`
: ringSize === 'screen' ? `
gap-2 px-16 py-8
ring-[3000px]
transition-all duration-1000 ease-in-out
`: ringSize === undefined ? `
gap-1 px-4 py-2
hover:ring-2 active:ring-2
transition-all duration-300 ease-in-out
`: ''}
hover:ring-black/90 dark:hover:ring-white/90
active:ring-black/90 dark:active:ring-white/90
rounded-lg
group
${className}
`}
{...props}
>
{children}
<ChevronRight
className={`
transition-all duration-300 ease-in-out
transform
group-hover:translate-x-1
group-active:translate-x-1
`}
/>
</button>
)
}
type WantToUseOption = 'smart' | 'private' | 'cheap' | 'all'
const VoidOnboardingContent = () => {
@@ -406,18 +468,42 @@ const VoidOnboardingContent = () => {
// page 1 state
const [wantToUseOption, setWantToUseOption] = useState<WantToUseOption>('smart')
// page 2 state
const [selectedProviderName, setSelectedProviderName] = useState<ProviderName | null>(null)
// Replace the single selectedProviderName with four separate states
// page 2 state - each tab gets its own state
const [selectedIntelligentProvider, setSelectedIntelligentProvider] = useState<ProviderName>('anthropic');
const [selectedPrivateProvider, setSelectedPrivateProvider] = useState<ProviderName>('ollama');
const [selectedAffordableProvider, setSelectedAffordableProvider] = useState<ProviderName>('gemini');
const [selectedAllProvider, setSelectedAllProvider] = useState<ProviderName>('anthropic');
// Helper function to get the current selected provider based on active tab
const getSelectedProvider = (): ProviderName => {
switch (wantToUseOption) {
case 'smart': return selectedIntelligentProvider;
case 'private': return selectedPrivateProvider;
case 'cheap': return selectedAffordableProvider;
case 'all': return selectedAllProvider;
}
}
// Helper function to set the selected provider for the current tab
const setSelectedProvider = (provider: ProviderName) => {
switch (wantToUseOption) {
case 'smart': setSelectedIntelligentProvider(provider); break;
case 'private': setSelectedPrivateProvider(provider); break;
case 'cheap': setSelectedAffordableProvider(provider); break;
case 'all': setSelectedAllProvider(provider); break;
}
}
const providerNamesOfWantToUseOption: { [wantToUseOption in WantToUseOption]: ProviderName[] } = {
smart: ['anthropic', 'openAI', 'gemini', 'openRouter'],
private: ['ollama', 'vLLM', 'openAICompatible'],
cheap: ['gemini', 'deepseek', 'openRouter', 'ollama', 'vLLM'],
all: providerNames,
// TODO allow user to redo onboarding
}
const selectedProviderName = getSelectedProvider();
const didFillInProviderSettings = selectedProviderName && voidSettingsState.settingsOfProvider[selectedProviderName]._didFillInProviderSettings
const isApiKeyLongEnoughIfApiKeyExists = selectedProviderName && voidSettingsState.settingsOfProvider[selectedProviderName].apiKey ? voidSettingsState.settingsOfProvider[selectedProviderName].apiKey.length > 15 : true
const isAtLeastOneModel = selectedProviderName && voidSettingsState.settingsOfProvider[selectedProviderName].models.length >= 1
@@ -425,7 +511,7 @@ const VoidOnboardingContent = () => {
const didFillInSelectedProviderSettings = !!(didFillInProviderSettings && isApiKeyLongEnoughIfApiKeyExists && isAtLeastOneModel)
const prevAndNextButtons = <div className="max-w-[600px] w-full mx-auto flex flex-col items-end">
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<PreviousButton
onClick={() => { setPageIndex(pageIndex - 1) }}
/>
@@ -437,11 +523,24 @@ const VoidOnboardingContent = () => {
</div>
const lastPagePrevAndNextButtons = <div className="max-w-[600px] w-full mx-auto flex flex-col items-end">
<div className="flex items-center gap-2">
<PreviousButton
onClick={() => { setPageIndex(pageIndex - 1) }}
/>
<PrimaryActionButton
onClick={() => { voidSettingsService.setGlobalSetting('isOnboardingComplete', true); }}
ringSize={voidSettingsState.globalSettings.isOnboardingComplete ? 'screen' : undefined}
>Enter the Void</PrimaryActionButton>
</div>
</div>
// cannot be md
const basicDescOfWantToUseOption: { [wantToUseOption in WantToUseOption]: string } = {
smart: "Models with the best performance on benchmarks.",
private: "Fully private and hosted on your computer/network.",
cheap: "Free and low-cost options.",
private: "Host on your computer or local network for full data privacy.",
cheap: "Free and affordable options.",
all: "",
}
@@ -449,24 +548,25 @@ const VoidOnboardingContent = () => {
const detailedDescOfWantToUseOption: { [wantToUseOption in WantToUseOption]: string } = {
smart: "Most intelligent and best for agent mode.",
private: "Private-hosted so your data never leaves your computer or network. [Email us](mailto:founders@voideditor.com) for help setting up at your company.",
cheap: "Great deals like Gemini 2.5 Pro or self-host a model with Ollama or vLLM for free.",
cheap: "Use great deals like Gemini 2.5 Pro, or self-host a model with Ollama or vLLM for free.",
all: "",
}
// set the selected provider name appropriately
// Modified: initialize separate provider states on initial render instead of watching wantToUseOption changes
useEffect(() => {
if (wantToUseOption && providerNamesOfWantToUseOption[wantToUseOption].length > 0) {
setSelectedProviderName(providerNamesOfWantToUseOption[wantToUseOption][0]);
} else {
setSelectedProviderName(null);
if (selectedIntelligentProvider === undefined) {
setSelectedIntelligentProvider(providerNamesOfWantToUseOption['smart'][0]);
}
}, [wantToUseOption]);
// set wantToUseOption to smart when page changes
useEffect(() => {
setWantToUseOption(wantToUseOption);
}, [pageIndex]);
if (selectedPrivateProvider === undefined) {
setSelectedPrivateProvider(providerNamesOfWantToUseOption['private'][0]);
}
if (selectedAffordableProvider === undefined) {
setSelectedAffordableProvider(providerNamesOfWantToUseOption['cheap'][0]);
}
if (selectedAllProvider === undefined) {
setSelectedAllProvider(providerNamesOfWantToUseOption['all'][0]);
}
}, []);
// reset the page to page 0 if the user redos onboarding
useEffect(() => {
@@ -476,168 +576,189 @@ const VoidOnboardingContent = () => {
}, [setPageIndex, voidSettingsState.globalSettings.isOnboardingComplete])
// TODO add a description next to the skip button saying (you can always restart the onboarding in Settings)
const contentOfIdx: { [pageIndex: number]: React.ReactNode } = {
// 0: <OnboardingPageShell
// top={
// <div className='bg-green-600 h-6 w-32' />
// }
// content={
// <div className='bg-red-600 h-[10000px] w-32' />
// }
// bottom={
// <div className='bg-blue-600 h-6 w-32' />
// }
// />,
0: <OnboardingPageShell
top={
<div className="text-5xl font-light text-center">Welcome to Void</div>
}
content={
<FadeIn
delayMs={500}
className="text-center"
onClick={() => { setPageIndex(pageIndex + 1) }}
>
Get Started
</FadeIn>
}
bottom={
''
<div className='flex flex-col items-center gap-8'>
<div className="text-5xl font-light text-center">Welcome to Void</div>
{/* Slice of Void image */}
<div className='max-w-md w-full h-[30vh] mx-auto flex items-center justify-center'>
<VoidIcon />
</div>
<FadeIn
delayMs={1000}
>
<PrimaryActionButton
onClick={() => { setPageIndex(pageIndex + 1) }}
>
Get Started
</PrimaryActionButton>
</FadeIn>
</div>
}
/>,
1: <OnboardingPageShell
hasMaxWidth={false}
top={
<FadeIn className='flex flex-col items-center'>
<div className="text-5xl font-light text-center">AI Preferences</div>
top={<></>}
content={<div className='flex flex-col items-center -translate-y-[20vh]'>
{/* <div className="text-5xl text-center mb-8">AI Preferences</div> */}
<div className="mt-[10%] text-base text-void-fg-2 mb-8 text-center">What are you looking for in an AI model?</div>
<div className="text-4xl text-void-fg-2 mb-8 text-center">Model Preferences</div>
<div className="flex justify-center w-full md:flex-nowrap md:max-w-[80%] max-w-[90%] gap-4">
<div
onClick={() => { setWantToUseOption('smart'); setPageIndex(pageIndex + 1); }}
className="w-full max-w-[250px] h-full relative p-6 aspect-[8/7] border border-void-border-1 rounded-md group flex flex-col items-center justify-center cursor-pointer"
>
<div className="absolute inset-0 bg-gradient-to-br from-[#0e70c0]/15 via-[#0e70c0]/5 to-transparent dark:from-[#0e70c0]/20 dark:via-[#0e70c0]/10 dark:to-[#0e70c0]/5 transition-opacity duration-300 ease-in-out opacity-100"></div>
<div className="absolute inset-0 bg-gradient-to-br from-[#0e70c0]/25 via-[#0e70c0]/10 to-[#0e70c0]/5 dark:from-[#0e70c0]/30 dark:via-[#0e70c0]/15 dark:to-[#0e70c0]/5 transition-opacity duration-300 ease-in-out opacity-0 group-hover:opacity-100"></div>
<span className="text-5xl mb-4 relative z-10">🧠</span>
<h3 className="text-xl font-medium mb-3 relative z-10">Intelligence</h3>
<p className="text-center text-root text-void-fg-2 relative z-10">{basicDescOfWantToUseOption['smart']}</p>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 w-full max-w-[800px] mx-auto mt-8">
<button
onClick={() => { setWantToUseOption('smart'); setPageIndex(pageIndex + 1); }}
className="flex flex-col p-6 rounded bg-void-bg-2 border border-void-border-3 hover:brightness-110 transition-colors focus:outline-none focus:border-void-accent-border relative overflow-hidden min-h-[160px]"
>
<div className="flex items-center mb-3">
<Brain size={24} className="text-void-fg-2 mr-2" />
<div className="text-lg font-medium text-void-fg-1">Intelligent</div>
</div>
<div className="text-sm text-void-fg-2 text-left">{basicDescOfWantToUseOption['smart']}</div>
</button>
<div
onClick={() => { setWantToUseOption('private'); setPageIndex(pageIndex + 1); }}
className="w-full max-w-[250px] h-full relative p-6 aspect-[8/7] border border-void-border-1 rounded-md group flex flex-col items-center justify-center cursor-pointer"
>
<div className="absolute inset-0 bg-gradient-to-br from-[#0e70c0]/15 via-[#0e70c0]/5 to-transparent dark:from-[#0e70c0]/20 dark:via-[#0e70c0]/10 dark:to-[#0e70c0]/5 transition-opacity duration-300 ease-in-out opacity-100"></div>
<div className="absolute inset-0 bg-gradient-to-br from-[#0e70c0]/25 via-[#0e70c0]/10 to-[#0e70c0]/5 dark:from-[#0e70c0]/30 dark:via-[#0e70c0]/15 dark:to-[#0e70c0]/5 transition-opacity duration-300 ease-in-out opacity-0 group-hover:opacity-100"></div>
<span className="text-5xl mb-4 relative z-10">🔒</span>
<h3 className="text-xl font-medium mb-3 relative z-10">Privacy</h3>
<p className="text-center text-root text-void-fg-2 relative z-10">{basicDescOfWantToUseOption['private']}</p>
<button
onClick={() => { setWantToUseOption('private'); setPageIndex(pageIndex + 1); }}
className="flex flex-col p-6 rounded bg-void-bg-2 border border-void-border-3 hover:brightness-110 transition-colors focus:outline-none focus:border-void-accent-border relative overflow-hidden min-h-[160px]"
>
<div className="flex items-center mb-3">
<Lock size={24} className="text-void-fg-2 mr-2" />
<div className="text-lg font-medium text-void-fg-1">Private</div>
</div>
<div className="text-sm text-void-fg-2 text-left">{basicDescOfWantToUseOption['private']}</div>
</button>
<div
onClick={() => { setWantToUseOption('cheap'); setPageIndex(pageIndex + 1); }}
className="w-full max-w-[250px] h-full relative p-6 aspect-[8/7] border border-void-border-1 rounded-md group flex flex-col items-center justify-center cursor-pointer"
>
<div className="absolute inset-0 bg-gradient-to-br from-[#0e70c0]/15 via-[#0e70c0]/5 to-transparent dark:from-[#0e70c0]/20 dark:via-[#0e70c0]/10 dark:to-[#0e70c0]/5 transition-opacity duration-300 ease-in-out opacity-100"></div>
<div className="absolute inset-0 bg-gradient-to-br from-[#0e70c0]/25 via-[#0e70c0]/10 to-[#0e70c0]/5 dark:from-[#0e70c0]/30 dark:via-[#0e70c0]/15 dark:to-[#0e70c0]/5 transition-opacity duration-300 ease-in-out opacity-0 group-hover:opacity-100"></div>
<span className="text-5xl mb-4 relative z-10">💵</span>
<h3 className="text-xl font-medium mb-3 relative z-10">Affordability</h3>
<p className="text-center text-root text-void-fg-2 relative z-10">{basicDescOfWantToUseOption['cheap']}</p>
<button
onClick={() => { setWantToUseOption('cheap'); setPageIndex(pageIndex + 1); }}
className="flex flex-col p-6 rounded bg-void-bg-2 border border-void-border-3 hover:brightness-110 transition-colors focus:outline-none focus:border-void-accent-border relative overflow-hidden min-h-[160px]"
>
<div className="flex items-center mb-3">
<DollarSign size={24} className="text-void-fg-2 mr-2" />
<div className="text-lg font-medium text-void-fg-1">Affordable</div>
</div>
</div>
<div className="text-sm text-void-fg-2 text-left">{basicDescOfWantToUseOption['cheap']}</div>
</button>
</div>
</FadeIn>
</div>}
bottom={
<div className='mx-auto w-full max-w-[800px]'>
<PreviousButton onClick={() => { setPageIndex(pageIndex - 1) }} />
</div>
}
content={<></>}
/>,
2: <OnboardingPageShell
top={
<div className='flex flex-col items-center'>
<>
{/* Title */}
<div className="text-5xl font-light text-center">Choose a Provider</div>
<div className="text-5xl font-light text-center mt-[10vh] mb-6">Choose a Provider</div>
{/* Preference Selector */}
<div className="mt-6 mb-6 mx-auto flex items-center overflow-hidden bg-zinc-700/5 dark:bg-zinc-300/5 rounded-md">
<button
onClick={() => {
setWantToUseOption('smart');
}}
className={`py-1 px-2 text-xs cursor-pointer whitespace-nowrap rounded-sm transition-colors
${wantToUseOption === 'smart'
? 'bg-zinc-700/10 dark:bg-zinc-300/10 text-white font-medium'
<div
className="mb-6 w-fit mx-auto flex items-center overflow-hidden bg-zinc-700/5 dark:bg-zinc-300/5 rounded-md"
>
{[
{ id: 'smart', label: 'Intelligent' },
{ id: 'private', label: 'Private' },
{ id: 'cheap', label: 'Affordable' },
{ id: 'all', label: 'All' }
].map(option => (
<button
key={option.id}
onClick={() => setWantToUseOption(option.id as WantToUseOption)}
className={`py-1 px-2 text-xs cursor-pointer whitespace-nowrap rounded-sm transition-colors ${wantToUseOption === option.id
? 'dark:text-white text-black font-medium'
: 'text-void-fg-3 hover:text-void-fg-2'
}
`}
>
Intelligent
</button>
<button
onClick={() => {
setWantToUseOption('private');
}}
className={`py-1 px-2 text-xs cursor-pointer whitespace-nowrap rounded-sm transition-colors
${wantToUseOption === 'private'
? 'bg-zinc-700/10 dark:bg-zinc-300/10 text-white font-medium'
: 'text-void-fg-3 hover:text-void-fg-2'
}
`}
>
Private
</button>
<button
onClick={() => {
setWantToUseOption('cheap');
}}
className={`py-1 px-2 text-xs cursor-pointer whitespace-nowrap rounded-sm transition-colors
${wantToUseOption === 'cheap'
? 'bg-zinc-700/10 dark:bg-zinc-300/10 text-white font-medium'
: 'text-void-fg-3 hover:text-void-fg-2'
}
`}
>
Low-Cost
</button>
<button
onClick={() => {
setWantToUseOption('all')
}}
className={`py-1 px-2 text-xs cursor-pointer whitespace-nowrap rounded-sm transition-colors
${wantToUseOption === 'all'
? 'bg-zinc-700/10 dark:bg-zinc-300/10 text-white font-medium'
: 'text-void-fg-3 hover:text-void-fg-2'
}
`}
>
All
</button>
}`}
data-tooltip-id='void-tooltip'
data-tooltip-content={`${option.label} providers`}
data-tooltip-place='bottom'
>
{option.label}
</button>
))}
</div>
{/* Provider Buttons */}
<div
key={wantToUseOption}
className="mb-2 flex flex-wrap items-center w-full"
>
{(wantToUseOption === 'all' ? providerNames : providerNamesOfWantToUseOption[wantToUseOption]).map((providerName) => {
const isSelected = selectedProviderName === providerName
{/* Provider Buttons - Modified to use separate components for each tab */}
<div className="mb-2 w-full">
{/* Intelligent tab */}
<div className={`flex flex-wrap items-center w-full ${wantToUseOption === 'smart' ? 'flex' : 'hidden'}`}>
{providerNamesOfWantToUseOption['smart'].map((providerName) => {
const isSelected = selectedIntelligentProvider === providerName;
return (
<button
key={providerName}
onClick={() => setSelectedIntelligentProvider(providerName)}
className={`py-[2px] px-2 mx-0.5 my-0.5 text-xs font-medium cursor-pointer relative rounded-full transition-all duration-300
${isSelected ? 'bg-zinc-100 text-zinc-900 shadow-sm border-white/80' : 'bg-zinc-100/40 hover:bg-zinc-100/50 text-zinc-900 border-white/20'}`}
>
{displayInfoOfProviderName(providerName).title}
</button>
);
})}
</div>
return (
<button
key={providerName}
onClick={() => setSelectedProviderName(providerName)}
className={`py-[2px] px-2 mx-0.5 my-0.5 text-xs font-medium cursor-pointer relative rounded-full transition-colors duration-150 border
${isSelected ? 'bg-[#0e70c0] text-white shadow-sm border-[#0e70c0]/80' : 'bg-[#0e70c0]/10 text-void-fg-3 hover:bg-[#0e70c0]/30 border-[#0e70c0]/20'}
`}
>
{displayInfoOfProviderName(providerName).title}
</button>
)
})}
{/* Private tab */}
<div className={`flex flex-wrap items-center w-full ${wantToUseOption === 'private' ? 'flex' : 'hidden'}`}>
{providerNamesOfWantToUseOption['private'].map((providerName) => {
const isSelected = selectedPrivateProvider === providerName;
return (
<button
key={providerName}
onClick={() => setSelectedPrivateProvider(providerName)}
className={`py-[2px] px-2 mx-0.5 my-0.5 text-xs font-medium cursor-pointer relative rounded-full transition-all duration-300
${isSelected ? 'bg-zinc-100 text-zinc-900 shadow-sm border-white/80' : 'bg-zinc-100/40 hover:bg-zinc-100/50 text-zinc-900 border-white/20'}`}
>
{displayInfoOfProviderName(providerName).title}
</button>
);
})}
</div>
{/* Affordable tab */}
<div className={`flex flex-wrap items-center w-full ${wantToUseOption === 'cheap' ? 'flex' : 'hidden'}`}>
{providerNamesOfWantToUseOption['cheap'].map((providerName) => {
const isSelected = selectedAffordableProvider === providerName;
return (
<button
key={providerName}
onClick={() => setSelectedAffordableProvider(providerName)}
className={`py-[2px] px-2 mx-0.5 my-0.5 text-xs font-medium cursor-pointer relative rounded-full transition-all duration-300
${isSelected ? 'bg-zinc-100 text-zinc-900 shadow-sm border-white/80' : 'bg-zinc-100/40 hover:bg-zinc-100/50 text-zinc-900 border-white/20'}`}
>
{displayInfoOfProviderName(providerName).title}
</button>
);
})}
</div>
{/* All tab */}
<div className={`flex flex-wrap items-center w-full ${wantToUseOption === 'all' ? 'flex' : 'hidden'}`}>
{providerNames.map((providerName) => {
const isSelected = selectedAllProvider === providerName;
return (
<button
key={providerName}
onClick={() => setSelectedAllProvider(providerName)}
className={`py-[2px] px-2 mx-0.5 my-0.5 text-xs font-medium cursor-pointer relative rounded-full transition-all duration-300
${isSelected ? 'bg-zinc-100 text-zinc-900 shadow-sm border-white/80' : 'bg-zinc-100/40 hover:bg-zinc-100/50 text-zinc-900 border-white/20'}`}
>
{displayInfoOfProviderName(providerName).title}
</button>
);
})}
</div>
</div>
{/* Description */}
@@ -647,7 +768,7 @@ const VoidOnboardingContent = () => {
{/* ModelsTable and ProviderFields */}
{selectedProviderName && <div className='mt-4'>
{selectedProviderName && <div className='mt-4 w-fit mx-auto'>
{/* Models Table */}
@@ -655,12 +776,13 @@ const VoidOnboardingContent = () => {
{/* Add provider section - simplified styling */}
<div className='mb-5 mt-8'>
<div className='mb-5 mt-8 mx-auto'>
<div className=''>
Add {displayInfoOfProviderName(selectedProviderName).title}
{selectedProviderName === 'ollama' ? ollamaSetupInstructions : ''}
<div className='my-4'>
{selectedProviderName === 'ollama' ? ollamaSetupInstructions : ''}
</div>
</div>
@@ -672,17 +794,18 @@ const VoidOnboardingContent = () => {
{!didFillInProviderSettings ? <p className="text-xs text-void-fg-3 mt-2">Please fill in all fields to continue</p>
: !isAtLeastOneModel ? <p className="text-xs text-void-fg-3 mt-2">Please add a model to continue</p>
: !isApiKeyLongEnoughIfApiKeyExists ? <p className="text-xs text-void-fg-3 mt-2">Please enter a valid API key</p>
: <div className="mt-2"><AnimatedCheckmarkButton text='Added' /></div>}
: <AnimatedCheckmarkButton className='text-xs text-void-fg-3 mt-2' text='Added' />}
</div>
</div>}
</div>
</>
}
bottom={
prevAndNextButtons
<FadeIn delayMs={50} durationMs={10}>
{prevAndNextButtons}
</FadeIn>
}
/>,
@@ -701,7 +824,8 @@ const VoidOnboardingContent = () => {
// {prevAndNextButtons}
// </div>,
3: <OnboardingPageShell
top={
content={
<div>
<div className="text-5xl font-light text-center">Settings and Themes</div>
@@ -713,34 +837,33 @@ const VoidOnboardingContent = () => {
</div>
</div>
}
bottom={prevAndNextButtons}
/>,
4: <OnboardingPageShell
top={
<div className="text-5xl font-light text-center">Jump in</div>
}
content={
<div
className="text-center"
onClick={() => {
// TODO make a fadeout effect
voidSettingsService.setGlobalSetting('isOnboardingComplete', true)
}}
>
Enter the Void
</div>
}
bottom={
<PreviousButton
onClick={() => { setPageIndex(pageIndex - 1) }}
/>
}
bottom={lastPagePrevAndNextButtons}
// bottom={prevAndNextButtons}
/>,
// 4: <OnboardingPageShell
// content={
// <>
// <div
// className='flex justify-center'
// >
// <PrimaryActionButton
// onClick={() => { voidSettingsService.setGlobalSetting('isOnboardingComplete', true); }}
// ringSize={voidSettingsState.globalSettings.isOnboardingComplete ? 'screen' : undefined}
// className='text-4xl'
// >Enter the Void</PrimaryActionButton>
// </div>
// </>
// }
// bottom={
// <PreviousButton
// onClick={() => { setPageIndex(pageIndex - 1) }}
// />
// }
// />,
}
return <div key={pageIndex} className="w-full h-full text-left mx-auto overflow-y-auto flex flex-col items-center justify-around">
return <div key={pageIndex} className="w-full h-full text-left mx-auto overflow-y-scroll flex flex-col items-center justify-around">
{contentOfIdx[pageIndex]}
</div>
@@ -12,6 +12,7 @@ import { IconWarning } from '../sidebar-tsx/SidebarChat.js'
import { VOID_OPEN_SETTINGS_ACTION_ID, VOID_TOGGLE_SETTINGS_ACTION_ID } from '../../../voidSettingsPane.js'
import { modelFilterOfFeatureName, ModelOption } from '../../../../../../../workbench/contrib/void/common/voidSettingsService.js'
import { WarningBox } from './WarningBox.js'
import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js'
const optionsEqual = (m1: ModelOption[], m2: ModelOption[]) => {
if (m1.length !== m2.length) return false
@@ -44,37 +45,6 @@ const ModelSelectBox = ({ options, featureName, className }: { options: ModelOpt
matchInputWidth={false}
/>
}
// const ModelSelectBox = ({ options, featureName }: { options: ModelOption[], featureName: FeatureName }) => {
// const accessor = useAccessor()
// const voidSettingsService = accessor.get('IVoidSettingsService')
// let weChangedText = false
// return <VoidSelectBox
// className='@@[&_select]:!void-text-xs text-void-fg-3'
// options={options}
// onChangeSelection={useCallback((newVal: ModelSelection) => {
// if (weChangedText) return
// voidSettingsService.setModelSelectionOfFeature(featureName, newVal)
// }, [voidSettingsService, featureName])}
// // we are responsible for setting the initial state here. always sync instance when state changes.
// onCreateInstance={useCallback((instance: SelectBox) => {
// const syncInstance = () => {
// const modelsListRef = voidSettingsService.state._modelOptions // as a ref
// const settingsAtProvider = voidSettingsService.state.modelSelectionOfFeature[featureName]
// const selectionIdx = settingsAtProvider === null ? -1 : modelsListRef.findIndex(v => modelSelectionsEqual(v.value, settingsAtProvider))
// weChangedText = true
// instance.select(selectionIdx === -1 ? 0 : selectionIdx)
// weChangedText = false
// }
// syncInstance()
// const disposable = voidSettingsService.onDidChangeState(syncInstance)
// return [disposable]
// }, [voidSettingsService, featureName])}
// />
// }
const MemoizedModelDropdown = ({ featureName, className }: { featureName: FeatureName, className: string }) => {
@@ -123,5 +93,7 @@ export const ModelDropdown = ({ featureName, className }: { featureName: Feature
: 'Provider required'
} />
return <MemoizedModelDropdown featureName={featureName} className={className} />
return <ErrorBoundary>
<MemoizedModelDropdown featureName={featureName} className={className} />
</ErrorBoundary>
}
@@ -121,7 +121,7 @@ export const AnimatedCheckmarkButton = ({ text, className }: { text?: string, cl
return <div
className={`flex items-center gap-1.5 w-fit
${className ? className : `px-2 py-0.5 text-xs text-white bg-[#0e70c0] rounded-sm`}
${className ? className : `px-2 py-0.5 text-xs text-zinc-900 bg-zinc-100 rounded-sm`}
`}
>
<svg className="size-4" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -146,7 +146,7 @@ const AddButton = ({ disabled, text = 'Add', ...props }: { disabled?: boolean, t
return <button
disabled={disabled}
className={`bg-[#0e70c0] px-3 py-1 text-white rounded-sm ${!disabled ? 'hover:bg-[#1177cb] cursor-pointer' : 'opacity-50 cursor-not-allowed bg-opacity-70'}`}
className={`bg-[#0e70c0] px-3 py-1 text-white dark:text-black rounded-sm ${!disabled ? 'hover:bg-[#1177cb] cursor-pointer' : 'opacity-50 cursor-not-allowed bg-opacity-70'}`}
{...props}
>{text}</button>
@@ -175,7 +175,7 @@ export const AddModelInputBox = ({ providerName: permanentProviderName, classNam
const numModels = settingsState.settingsOfProvider[providerName].models.length
if (showCheckmark) {
return <AnimatedCheckmarkButton text='Added' className={`bg-[#0e70c0] text-white px-3 py-1 rounded-sm ${className}`} />
return <AnimatedCheckmarkButton text='Added' className={`bg-[#0e70c0] text-white dark:text-black px-3 py-1 rounded-sm ${className}`} />
}
if (!isOpen) {
@@ -549,7 +549,12 @@ export const ollamaSetupInstructions = <div className='prose-p:my-0 prose-ol:lis
<div className=''><ChatMarkdownRender string={`Ollama Setup Instructions`} chatMessageLocation={undefined} /></div>
<div className=' pl-6'><ChatMarkdownRender string={`1. Download [Ollama](https://ollama.com/download).`} chatMessageLocation={undefined} /></div>
<div className=' pl-6'><ChatMarkdownRender string={`2. Open your terminal.`} chatMessageLocation={undefined} /></div>
<div className=' pl-6'><ChatMarkdownRender string={`3. Run \`ollama pull your_model\` to install a model.`} chatMessageLocation={undefined} /></div>
<div
className='pl-6 flex items-center w-fit'
data-tooltip-id='void-tooltip-ollama-settings'
>
<ChatMarkdownRender string={`3. Run \`ollama pull your_model\` to install a model.`} chatMessageLocation={undefined} />
</div>
<div className=' pl-6'><ChatMarkdownRender string={`Void automatically detects locally running models and enables them.`} chatMessageLocation={undefined} /></div>
</div>
@@ -567,182 +572,6 @@ const RedoOnboardingButton = ({ className }: { className?: string }) => {
}
export const FeaturesTab = () => {
const voidSettingsState = useSettingsState()
const accessor = useAccessor()
const voidSettingsService = accessor.get('IVoidSettingsService')
return <>
<h2 className={`text-3xl mb-2`}>Models</h2>
<ErrorBoundary>
<ModelDump />
<AddModelInputBox className='mt-4' compact />
<RedoOnboardingButton className='mt-2 mb-4' />
<AutoDetectLocalModelsToggle />
<RefreshableModels />
</ErrorBoundary>
<h2 className={`text-3xl mb-2 mt-12`}>Local Providers</h2>
{/* <h3 className={`opacity-50 mb-2`}>{`Keep your data private by hosting AI locally on your computer.`}</h3> */}
{/* <h3 className={`opacity-50 mb-2`}>{`Instructions:`}</h3> */}
{/* <h3 className={`mb-2`}>{`Void can access any model that you host locally. We automatically detect your local models by default.`}</h3> */}
<h3 className={`text-void-fg-3 mb-2`}>{`Void can access any model that you host locally. We automatically detect your local models by default.`}</h3>
<div className='opacity-80 mb-4'>
{ollamaSetupInstructions}
</div>
<ErrorBoundary>
<VoidProviderSettings providerNames={localProviderNames} />
</ErrorBoundary>
<h2 className={`text-3xl mb-2 mt-12`}>Providers</h2>
<h3 className={`text-void-fg-3 mb-2`}>{`Void can access models from Anthropic, OpenAI, OpenRouter, and more.`}</h3>
{/* <h3 className={`opacity-50 mb-2`}>{`Access models like ChatGPT and Claude. We recommend using Anthropic or OpenAI as providers, or Groq as a faster alternative.`}</h3> */}
<ErrorBoundary>
<VoidProviderSettings providerNames={nonlocalProviderNames} />
</ErrorBoundary>
<h2 className={`text-3xl mt-12`}>Feature Options</h2>
<ErrorBoundary>
{/* L1 */}
<div className='flex items-start justify-around mt-4 my-4 gap-x-8'>
{/* FIM */}
<div className='w-full'>
<h4 className={`text-base`}>{displayInfoOfFeatureName('Autocomplete')}</h4>
<div className='text-sm italic text-void-fg-3 mt-1 mb-4'>
<span>
Experimental. Only works with FIM models.
</span>
<span
className='hover:brightness-110'
data-tooltip-id='void-tooltip'
data-tooltip-content='We recommend using qwen2.5-coder:1.5b with Ollama.'
data-tooltip-class-name='void-max-w-[20px]'
>
*
</span>
</div>
<div className='my-2'>
{/* Enable Switch */}
<div className='flex items-center gap-x-2 my-2'>
<VoidSwitch
size='xs'
value={voidSettingsState.globalSettings.enableAutocomplete}
onChange={(newVal) => voidSettingsService.setGlobalSetting('enableAutocomplete', newVal)}
/>
<span className='text-void-fg-3 text-xs pointer-events-none'>{voidSettingsState.globalSettings.enableAutocomplete ? 'Enabled' : 'Disabled'}</span>
</div>
{/* Model Dropdown */}
<div className={`my-2 ${!voidSettingsState.globalSettings.enableAutocomplete ? 'hidden' : ''}`}>
<ModelDropdown featureName={'Autocomplete'} className='text-xs text-void-fg-3 bg-void-bg-1 border border-void-border-1 rounded p-0.5 px-1' />
</div>
</div>
</div>
{/* Apply */}
<div className='w-full'>
<h4 className={`text-base`}>{displayInfoOfFeatureName('Apply')}</h4>
<div className='text-sm italic text-void-fg-3 mt-1 mb-4'>Settings that control the behavior of the Apply button and the Edit tool.</div>
<div className='my-2'>
{/* Sync to Chat Switch */}
<div className='flex items-center gap-x-2 my-2'>
<VoidSwitch
size='xs'
value={voidSettingsState.globalSettings.syncApplyToChat}
onChange={(newVal) => voidSettingsService.setGlobalSetting('syncApplyToChat', newVal)}
/>
<span className='text-void-fg-3 text-xs pointer-events-none'>{voidSettingsState.globalSettings.syncApplyToChat ? 'Same as Chat model' : 'Different model'}</span>
</div>
{/* Model Dropdown */}
<div className={`my-2 ${voidSettingsState.globalSettings.syncApplyToChat ? 'hidden' : ''}`}>
<ModelDropdown featureName={'Apply'} className='text-xs text-void-fg-3 bg-void-bg-1 border border-void-border-1 rounded p-0.5 px-1' />
</div>
</div>
<div className='my-2'>
{/* Fast Apply Method Dropdown */}
<div className='flex items-center gap-x-2 my-2'>
<FastApplyMethodDropdown />
</div>
</div>
</div>
</div>
{/* L2 */}
<div className='flex items-start justify-around my-4 gap-x-8'>
{/* Tools Section */}
<div className='w-full'>
<h4 className={`text-base`}>Tools</h4>
<div className='text-sm italic text-void-fg-3 mt-1 mb-4'>{`Tools are functions that LLMs can call. Some tools require user approval.`}</div>
<div className='my-2'>
{/* Auto Accept Switch */}
<div className='flex items-center gap-x-2 my-2'>
<VoidSwitch
size='xs'
value={voidSettingsState.globalSettings.autoApprove}
onChange={(newVal) => voidSettingsService.setGlobalSetting('autoApprove', newVal)}
/>
<span className='text-void-fg-3 text-xs pointer-events-none'>{voidSettingsState.globalSettings.autoApprove ? 'Auto-approve' : 'Auto-approve'}</span>
</div>
{/* Tool Lint Errors Switch */}
<div className='flex items-center gap-x-2 my-2'>
<VoidSwitch
size='xs'
value={voidSettingsState.globalSettings.includeToolLintErrors}
onChange={(newVal) => voidSettingsService.setGlobalSetting('includeToolLintErrors', newVal)}
/>
<span className='text-void-fg-3 text-xs pointer-events-none'>{voidSettingsState.globalSettings.includeToolLintErrors ? 'Fix lint errors' : `Don't fix lint errors`}</span>
</div>
</div>
</div>
<div className='w-full'>
<h4 className={`text-base`}>Editor</h4>
<div className='text-sm italic text-void-fg-3 mt-1 mb-4'>{`Settings that control the visibility of suggestions and widgets in the code editor.`}</div>
<div className='my-2'>
{/* Auto Accept Switch */}
<div className='flex items-center gap-x-2 my-2'>
<VoidSwitch
size='xs'
value={voidSettingsState.globalSettings.showInlineSuggestions}
onChange={(newVal) => voidSettingsService.setGlobalSetting('showInlineSuggestions', newVal)}
/>
<span className='text-void-fg-3 text-xs pointer-events-none'>{voidSettingsState.globalSettings.showInlineSuggestions ? 'Show suggestions on select' : 'Show suggestions on select'}</span>
</div>
</div>
</div>
</div>
<div className='py-8' />
</ErrorBoundary>
</>
}
type TransferEditorType = 'VS Code' | 'Cursor' | 'Windsurf'
// https://github.com/VSCodium/vscodium/blob/master/docs/index.md#migrating-from-visual-studio-code-to-vscodium
// https://code.visualstudio.com/docs/editor/extension-marketplace#_where-are-extensions-installed
@@ -949,113 +778,264 @@ export const OneClickSwitchButton = ({ fromEditor = 'VS Code', className = '' }:
}
const GeneralTab = () => {
const accessor = useAccessor()
const commandService = accessor.get('ICommandService')
const environmentService = accessor.get('IEnvironmentService')
const nativeHostService = accessor.get('INativeHostService')
return <>
<div className=''>
<h2 className={`text-3xl mb-2`}>One-Click Switch</h2>
<h4 className={`text-void-fg-3 mb-4`}>{`Transfer your settings from another editor to Void in one click.`}</h4>
<div className='flex flex-col gap-4'>
<OneClickSwitchButton className='w-48' fromEditor="VS Code" />
<OneClickSwitchButton className='w-48' fromEditor="Cursor" />
<OneClickSwitchButton className='w-48' fromEditor="Windsurf" />
</div>
</div>
<div className='mt-12'>
<h2 className={`text-3xl mb-2`}>Built-in Settings</h2>
<h4 className={`text-void-fg-3 mb-4`}>{`IDE settings, keyboard settings, and theme customization.`}</h4>
<div className='my-4'>
<VoidButtonBgDarken className='px-4 py-2' onClick={() => { commandService.executeCommand('workbench.action.openSettings') }}>
General Settings
</VoidButtonBgDarken>
</div>
<div className='my-4'>
<VoidButtonBgDarken className='px-4 py-2' onClick={() => { commandService.executeCommand('workbench.action.openGlobalKeybindings') }}>
Keyboard Settings
</VoidButtonBgDarken>
</div>
<div className='my-4'>
<VoidButtonBgDarken className='px-4 py-2' onClick={() => { commandService.executeCommand('workbench.action.selectTheme') }}>
Theme Settings
</VoidButtonBgDarken>
</div>
<div className='my-4'>
<VoidButtonBgDarken className='px-4 py-2' onClick={() => { nativeHostService.showItemInFolder(environmentService.logsHome.fsPath) }}>
Open Logs
</VoidButtonBgDarken>
</div>
</div>
<div className='mt-12 max-w-[600px]'>
<h2 className={`text-3xl mb-2`}>AI Instructions</h2>
<h4 className={`text-void-fg-3 mb-4`}>{`Instructions to include on all AI requests.`}</h4>
<AIInstructionsBox />
</div>
</>
}
// full settings
export const Settings = () => {
const isDark = useIsDark()
const [tab, setTab] = useState<TabName>('models')
const accessor = useAccessor()
const commandService = accessor.get('ICommandService')
const environmentService = accessor.get('IEnvironmentService')
const nativeHostService = accessor.get('INativeHostService')
const settingsState = useSettingsState()
const voidSettingsService = accessor.get('IVoidSettingsService')
return <div className={`@@void-scope ${isDark ? 'dark' : ''}`} style={{ height: '100%', width: '100%' }}>
<div className='overflow-y-auto w-full h-full px-10 py-10 select-none'>
<div className='max-w-5xl mx-auto'>
<div className='max-w-xl mx-auto'>
<h1 className='text-2xl w-full'>{`Void's Settings`}</h1>
{/* separator */}
<div className='w-full h-[1px] my-4' />
<div className='flex items-stretch'>
{/* Models section (formerly FeaturesTab) */}
<ErrorBoundary>
<h2 className={`text-3xl mb-2`}>Models</h2>
<ModelDump />
<AddModelInputBox className='mt-4' compact />
<RedoOnboardingButton className='mt-2 mb-4' />
<AutoDetectLocalModelsToggle />
<RefreshableModels />
</ErrorBoundary>
{/* tabs */}
<div className='flex flex-col w-full max-w-32'>
<button className={`text-left p-1 px-3 my-0.5 rounded-sm overflow-hidden ${tab === 'models' ? 'bg-black/10 dark:bg-gray-200/10' : ''} hover:bg-black/10 hover:dark:bg-gray-200/10 active:bg-black/10 active:dark:bg-gray-200/10 `}
onClick={() => { setTab('models') }}
>Models</button>
<button className={`text-left p-1 px-3 my-0.5 rounded-sm overflow-hidden ${tab === 'general' ? 'bg-black/10 dark:bg-gray-200/10' : ''} hover:bg-black/10 hover:dark:bg-gray-200/10 active:bg-black/10 active:dark:bg-gray-200/10 `}
onClick={() => { setTab('general') }}
>General</button>
<h2 className={`text-3xl mb-2 mt-12`}>Local Providers</h2>
<h3 className={`text-void-fg-3 mb-2`}>{`Void can access any model that you host locally. We automatically detect your local models by default.`}</h3>
<div className='opacity-80 mb-4'>
{ollamaSetupInstructions}
</div>
<ErrorBoundary>
<VoidProviderSettings providerNames={localProviderNames} />
</ErrorBoundary>
<h2 className={`text-3xl mb-2 mt-12`}>Providers</h2>
<h3 className={`text-void-fg-3 mb-2`}>{`Void can access models from Anthropic, OpenAI, OpenRouter, and more.`}</h3>
<ErrorBoundary>
<VoidProviderSettings providerNames={nonlocalProviderNames} />
</ErrorBoundary>
<h2 className={`text-3xl mt-12`}>Feature Options</h2>
{/* L1 */}
<div className='flex items-start justify-around my-4 gap-x-8'>
<ErrorBoundary>
{/* FIM */}
<div className='w-full'>
<h4 className={`text-base`}>{displayInfoOfFeatureName('Autocomplete')}</h4>
<div className='text-sm italic text-void-fg-3 mt-1 mb-4'>
<span>
Experimental.{' '}
</span>
<span
className='hover:brightness-110'
data-tooltip-id='void-tooltip'
data-tooltip-content='We recommend using qwen2.5-coder:1.5b with Ollama.'
data-tooltip-class-name='void-max-w-[20px]'
>
Only works with FIM models.*
</span>
</div>
<div className='my-2'>
{/* Enable Switch */}
<ErrorBoundary>
<div className='flex items-center gap-x-2 my-2'>
<VoidSwitch
size='xs'
value={settingsState.globalSettings.enableAutocomplete}
onChange={(newVal) => voidSettingsService.setGlobalSetting('enableAutocomplete', newVal)}
/>
<span className='text-void-fg-3 text-xs pointer-events-none'>{settingsState.globalSettings.enableAutocomplete ? 'Enabled' : 'Disabled'}</span>
</div>
</ErrorBoundary>
{/* Model Dropdown */}
<ErrorBoundary>
<div className={`my-2 ${!settingsState.globalSettings.enableAutocomplete ? 'hidden' : ''}`}>
<ModelDropdown featureName={'Autocomplete'} className='text-xs text-void-fg-3 bg-void-bg-1 border border-void-border-1 rounded p-0.5 px-1' />
</div>
</ErrorBoundary>
</div>
</div>
</ErrorBoundary>
{/* Apply */}
<ErrorBoundary>
<div className='w-full'>
<h4 className={`text-base`}>{displayInfoOfFeatureName('Apply')}</h4>
<div className='text-sm italic text-void-fg-3 mt-1 mb-4'>Settings that control the behavior of the Apply button and the Edit tool.</div>
<div className='my-2'>
{/* Sync to Chat Switch */}
<div className='flex items-center gap-x-2 my-2'>
<VoidSwitch
size='xs'
value={settingsState.globalSettings.syncApplyToChat}
onChange={(newVal) => voidSettingsService.setGlobalSetting('syncApplyToChat', newVal)}
/>
<span className='text-void-fg-3 text-xs pointer-events-none'>{settingsState.globalSettings.syncApplyToChat ? 'Same as Chat model' : 'Different model'}</span>
</div>
{/* Model Dropdown */}
<div className={`my-2 ${settingsState.globalSettings.syncApplyToChat ? 'hidden' : ''}`}>
<ModelDropdown featureName={'Apply'} className='text-xs text-void-fg-3 bg-void-bg-1 border border-void-border-1 rounded p-0.5 px-1' />
</div>
</div>
<div className='my-2'>
{/* Fast Apply Method Dropdown */}
<div className='flex items-center gap-x-2 my-2'>
<FastApplyMethodDropdown />
</div>
</div>
</div>
</ErrorBoundary>
</div>
{/* L2 */}
<div className='flex items-start justify-around my-4 gap-x-8'>
{/* Tools Section */}
<div className='w-full'>
<h4 className={`text-base`}>Tools</h4>
<div className='text-sm italic text-void-fg-3 mt-1 mb-4'>{`Tools are functions that LLMs can call. Some tools require user approval.`}</div>
<div className='my-2'>
{/* Auto Accept Switch */}
<ErrorBoundary>
<div className='flex items-center gap-x-2 my-2'>
<VoidSwitch
size='xs'
value={settingsState.globalSettings.autoApprove}
onChange={(newVal) => voidSettingsService.setGlobalSetting('autoApprove', newVal)}
/>
<span className='text-void-fg-3 text-xs pointer-events-none'>{settingsState.globalSettings.autoApprove ? 'Auto-approve' : 'Auto-approve'}</span>
</div>
</ErrorBoundary>
{/* Tool Lint Errors Switch */}
<ErrorBoundary>
<div className='flex items-center gap-x-2 my-2'>
<VoidSwitch
size='xs'
value={settingsState.globalSettings.includeToolLintErrors}
onChange={(newVal) => voidSettingsService.setGlobalSetting('includeToolLintErrors', newVal)}
/>
<span className='text-void-fg-3 text-xs pointer-events-none'>{settingsState.globalSettings.includeToolLintErrors ? 'Fix lint errors' : `Fix lint errors`}</span>
</div>
</ErrorBoundary>
</div>
</div>
{/* separator */}
<div className='w-[1px] mx-4' />
{/* content */}
<div className='w-full min-w-[550px]'>
<div className='w-full'>
<h4 className={`text-base`}>Editor</h4>
<div className='text-sm italic text-void-fg-3 mt-1 mb-4'>{`Settings that control the visibility of suggestions and widgets in the code editor.`}</div>
<div className={`${tab !== 'models' ? 'hidden' : ''}`}>
<FeaturesTab />
<div className='my-2'>
{/* Auto Accept Switch */}
<ErrorBoundary>
<div className='flex items-center gap-x-2 my-2'>
<VoidSwitch
size='xs'
value={settingsState.globalSettings.showInlineSuggestions}
onChange={(newVal) => voidSettingsService.setGlobalSetting('showInlineSuggestions', newVal)}
/>
<span className='text-void-fg-3 text-xs pointer-events-none'>{settingsState.globalSettings.showInlineSuggestions ? 'Show suggestions on select' : 'Show suggestions on select'}</span>
</div>
</ErrorBoundary>
</div>
<div className={`${tab !== 'general' ? 'hidden' : ''}`}>
<GeneralTab />
</div>
</div>
</div>
{/* General section (formerly GeneralTab) */}
<div className='mt-12'>
<ErrorBoundary>
<h2 className={`text-3xl mb-2 mt-12`}>One-Click Switch</h2>
<h4 className={`text-void-fg-3 mb-4`}>{`Transfer your settings from another editor to Void in one click.`}</h4>
<div className='flex flex-col gap-4'>
<OneClickSwitchButton className='w-48' fromEditor="VS Code" />
<OneClickSwitchButton className='w-48' fromEditor="Cursor" />
<OneClickSwitchButton className='w-48' fromEditor="Windsurf" />
</div>
</ErrorBoundary>
</div>
<div className='mt-12'>
<h2 className={`text-3xl mb-2`}>Built-in Settings</h2>
<h4 className={`text-void-fg-3 mb-4`}>{`IDE settings, keyboard settings, and theme customization.`}</h4>
<ErrorBoundary>
<div className='my-4'>
<VoidButtonBgDarken className='px-4 py-2' onClick={() => { commandService.executeCommand('workbench.action.openSettings') }}>
General Settings
</VoidButtonBgDarken>
</div>
<div className='my-4'>
<VoidButtonBgDarken className='px-4 py-2' onClick={() => { commandService.executeCommand('workbench.action.openGlobalKeybindings') }}>
Keyboard Settings
</VoidButtonBgDarken>
</div>
<div className='my-4'>
<VoidButtonBgDarken className='px-4 py-2' onClick={() => { commandService.executeCommand('workbench.action.selectTheme') }}>
Theme Settings
</VoidButtonBgDarken>
</div>
<div className='my-4'>
<VoidButtonBgDarken className='px-4 py-2' onClick={() => { nativeHostService.showItemInFolder(environmentService.logsHome.fsPath) }}>
Open Logs
</VoidButtonBgDarken>
</div>
</ErrorBoundary>
</div>
<div className='mt-12 max-w-[600px]'>
<h2 className={`text-3xl mb-2`}>AI Instructions</h2>
<h4 className={`text-void-fg-3 mb-4`}>
<ChatMarkdownRender inPTag={true} string={`
System instructions to include with all AI requests.
Alternatively, place a \`.voidinstructions\` file in the root of your workspace.
`} chatMessageLocation={undefined} />
</h4>
<ErrorBoundary>
<AIInstructionsBox />
</ErrorBoundary>
</div>
</div>
</div>
</div>
}
@@ -3,6 +3,7 @@
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import '../styles.css'
import { Tooltip } from 'react-tooltip';
import 'react-tooltip/dist/react-tooltip.css';
import { useIsDark } from '../util/services.js';
@@ -44,7 +45,7 @@ export const VoidTooltip = () => {
<>
<style>
{`
#void-tooltip, #void-tooltip-orange, #void-tooltip-green {
#void-tooltip, #void-tooltip-orange, #void-tooltip-green, #void-tooltip-ollama-settings {
font-size: 12px;
padding: 0px 8px;
border-radius: 6px;
@@ -66,6 +67,11 @@ export const VoidTooltip = () => {
color: white;
}
#void-tooltip-ollama-settings {
background-color: var(--vscode-editor-background);
color: var(--vscode-input-foreground);
}
.react-tooltip-arrow {
z-index: -1 !important; /* Keep arrow behind content (somehow this isnt done automatically) */
}
@@ -92,6 +98,28 @@ export const VoidTooltip = () => {
opacity={1}
delayShow={50}
/>
<Tooltip
id="void-tooltip-ollama-settings"
border='1px solid rgba(100,100,100,.2)'
opacity={1}
openEvents={{ mouseover: true, click: true, focus: true }}
place='right'
style={{ pointerEvents: 'all', userSelect: 'text', fontSize: 11 }}
>
<div style={{ padding: '8px 10px' }}>
<div style={{ opacity: 0.8, textAlign: 'center', fontWeight: 'bold', marginBottom: 8 }}>
Good starter models
</div>
<div style={{ marginBottom: 4 }}>
<span style={{ opacity: 0.8 }}>For chat:{` `}</span>
<span style={{ opacity: 0.8, fontWeight: 'bold' }}>llama3.1</span>
</div>
<div>
<span style={{ opacity: 0.8 }}>For autocomplete:{` `}</span>
<span style={{ opacity: 0.8, fontWeight: 'bold' }}>qwen2.5-coder:1.5b</span>
</div>
</div>
</Tooltip>
</>
);
};
@@ -53,6 +53,7 @@ module.exports = {
'void-bg-1-alt': 'var(--void-bg-1-alt)',
'void-bg-2': 'var(--void-bg-2)',
'void-bg-2-alt': 'var(--void-bg-2-alt)',
'void-bg-2-hover': 'var(--void-bg-2-hover)',
'void-bg-3': 'var(--void-bg-3)',
@@ -34,7 +34,7 @@ type ToolResultToString = { [T in ToolName]: (p: ToolCallParams[T], result: Awai
// pagination info
export const MAX_FILE_CHARS_PAGE = Infinity
export const MAX_FILE_CHARS_PAGE = 500_000
export const MAX_CHILDREN_URIs_PAGE = 500
export const MAX_TERMINAL_CHARS_PAGE = 20_000
export const TERMINAL_TIMEOUT_TIME = 5 // seconds
@@ -248,7 +248,7 @@ export class ToolsService implements IToolsService {
return { uri, changeDescription }
},
run_terminal_command: (params: RawToolParamsObj) => {
command_tool: (params: RawToolParamsObj) => {
const { command: commandUnknown, terminal_id: terminalIdUnknown, wait_for_completion: waitForCompletionUnknown } = params
const command = validateStr('command', commandUnknown)
const proposedTerminalId = validateProposedTerminalId(terminalIdUnknown)
@@ -385,7 +385,7 @@ export class ToolsService implements IToolsService {
return { result: lintErrorsPromise, interruptTool }
},
run_terminal_command: async ({ command, proposedTerminalId, waitForCompletion }) => {
command_tool: async ({ command, proposedTerminalId, waitForCompletion }) => {
const { terminalId, didCreateTerminal, result, resolveReason } = await this.terminalToolService.runCommand(command, proposedTerminalId, waitForCompletion)
return { result: { terminalId, didCreateTerminal, result, resolveReason } }
},
@@ -398,6 +398,7 @@ export class ToolsService implements IToolsService {
return lintErrors
.map((e, i) => `Error ${i + 1}:\nLines Affected: ${e.startLineNumber}-${e.endLineNumber}\nError message:${e.message}`)
.join('\n\n')
.substring(0, MAX_FILE_CHARS_PAGE)
}
// given to the LLM after the call
@@ -439,7 +440,7 @@ export class ToolsService implements IToolsService {
return `Change successfully made to ${params.uri.fsPath}.${lintErrsString}`
},
run_terminal_command: (params, result) => {
command_tool: (params, result) => {
const {
terminalId,
didCreateTerminal,
@@ -475,6 +476,7 @@ export class ToolsService implements IToolsService {
const lintErrors = this.markerService
.read({ resource: uri })
.filter(l => l.severity === MarkerSeverity.Error || l.severity === MarkerSeverity.Warning)
.slice(0, 100)
.map(l => ({
code: typeof l.code === 'string' ? l.code : l.code?.value || '',
message: (l.severity === MarkerSeverity.Error ? '(error) ' : '(warning) ') + l.message,
@@ -32,6 +32,7 @@ import './media/void.css'
// update (frontend part, also see platform/)
import './voidUpdateActions.js'
import './convertToLLMMessageWorkbenchContrib.js'
// tools
import './toolsService.js'
@@ -362,8 +362,6 @@ export class VoidCommandBarService extends Disposable implements IVoidCommandBar
registerSingleton(IVoidCommandBarService, VoidCommandBarService, InstantiationType.Delayed); // delayed is needed here :(
// registerWorkbenchContribution2(VoidCommandBarService.ID, VoidCommandBarService, WorkbenchPhase.BlockRestore);
export type VoidCommandBarProps = {
uri: URI | null;
@@ -99,7 +99,7 @@ const notifyUpdate = (res: VoidCheckUpdateRespose & { message: string }, notifSe
secondary: [{
id: 'void.updater.close',
enabled: true,
label: `Keep Void outdated`,
label: `Keep current version`,
tooltip: '',
class: undefined,
run: () => {
@@ -54,7 +54,8 @@ export const defaultModelsOfProvider = {
'gpt-4.1',
'gpt-4.1-mini',
'gpt-4.1-nano',
'o3-mini',
'o3',
'o4-mini',
// 'o1',
// 'o1-mini',
// 'gpt-4o',
@@ -358,6 +359,9 @@ const extensiveModelFallback: VoidStaticProviderInfo['modelOptionsFallback'] = (
if (lower.includes('o1') && lower.includes('mini')) return toFallback(openAIModelOptions['o1-mini'])
if (lower.includes('o1')) return toFallback(openAIModelOptions['o1'])
if (lower.includes('o3') && lower.includes('mini')) return toFallback(openAIModelOptions['o3-mini'])
if (lower.includes('o3')) return toFallback(openAIModelOptions['o3'])
if (lower.includes('o4') && lower.includes('mini')) return toFallback(openAIModelOptions['o4-mini'])
if (Object.keys(openSourceModelOptions_assumingOAICompat).map(k => k.toLowerCase()).includes(lower))
return toFallback(openSourceModelOptions_assumingOAICompat[lower as keyof typeof openSourceModelOptions_assumingOAICompat])
@@ -458,6 +462,26 @@ const anthropicSettings: VoidStaticProviderInfo = {
// ---------------- OPENAI ----------------
const openAIModelOptions = { // https://platform.openai.com/docs/pricing
'o3': {
contextWindow: 1_047_576,
maxOutputTokens: 32_768,
cost: { input: 10.00, output: 40.00, cache_read: 2.50 },
downloadable: false,
supportsFIM: false,
specialToolFormat: 'openai-style',
supportsSystemMessage: 'developer-role',
reasoningCapabilities: false,
},
'o4-mini': {
contextWindow: 1_047_576,
maxOutputTokens: 32_768,
cost: { input: 1.10, output: 4.40, cache_read: 0.275 },
downloadable: false,
supportsFIM: false,
specialToolFormat: 'openai-style',
supportsSystemMessage: 'developer-role',
reasoningCapabilities: false,
},
'gpt-4.1': {
contextWindow: 1_047_576,
maxOutputTokens: 32_768,
@@ -783,7 +807,7 @@ const groqSettings: VoidStaticProviderInfo = {
}
const ollamaModelOptions = {
'qwen2.5-coder:3b': {
'qwen2.5-coder:1.5b': {
contextWindow: 32_000,
maxOutputTokens: null,
cost: { input: 0, output: 0 },
@@ -792,6 +816,15 @@ const ollamaModelOptions = {
supportsSystemMessage: 'system-role',
reasoningCapabilities: false,
},
'llama3.1': {
contextWindow: 128_000,
maxOutputTokens: null,
cost: { input: 0, output: 0 },
downloadable: { sizeGb: 4.9 },
supportsFIM: false,
supportsSystemMessage: 'system-role',
reasoningCapabilities: false,
},
'qwen2.5-coder': {
contextWindow: 128_000,
maxOutputTokens: null,
@@ -822,7 +855,7 @@ const ollamaModelOptions = {
} as const satisfies Record<string, VoidStaticModelInfo>
export const ollamaRecommendedModels = ['qwen2.5-coder:3b', 'qwq', 'deepseek-r1'] as const satisfies (keyof typeof ollamaModelOptions)[]
export const ollamaRecommendedModels = ['qwen2.5-coder:1.5b', 'llama3.1', 'qwq', 'deepseek-r1'] as const satisfies (keyof typeof ollamaModelOptions)[]
@@ -166,11 +166,11 @@ Here's an example of a good description:\n${editToolDescriptionExample}`
},
},
run_terminal_command: {
name: 'run_terminal_command',
description: `Executes a terminal command.`,
command_tool: {
name: 'command_tool',
description: `Runs a terminal command. You can use this tool to run any command: sed, grep, etc. We just prefer you edit with the edit tool, not this tool if possible.`,
params: {
command: { description: 'The terminal command to execute. If working with tools like git that can paginate, you should pipe to cat so results are not truncated.' },
command: { description: 'The terminal command to run.' },
wait_for_completion: { description: `Optional. Default is true. Make this value false when you want a command to run without waiting for it to complete.` },
terminal_id: { description: 'Optional. The ID of the terminal instance that should execute the command (if not provided, defaults to the preferred terminal ID). The primary purpose of this is to let you open a new terminal for testing or background processes (e.g. running a dev server for the user in a separate terminal). Must be an integer >= 1.' },
},
@@ -205,7 +205,7 @@ export const availableTools = (chatMode: ChatMode) => {
return tools
}
const availableXMLToolsStr = (tools: InternalToolInfo[]) => {
const toolCallDefinitionsXMLString = (tools: InternalToolInfo[]) => {
return `${tools.map((t, i) => {
const params = Object.keys(t.params).map(paramName => `<${paramName}>${t.params[paramName].description}</${paramName}>`).join('\n')
return `\
@@ -217,7 +217,7 @@ Format:
}).join('\n\n')}`
}
export const toolCallXMLStr = (toolName: ToolName, toolParams: RawToolParamsObj) => {
export const reParsedToolXMLString = (toolName: ToolName, toolParams: RawToolParamsObj) => {
const params = Object.keys(toolParams).map(paramName => `<${paramName}>${toolParams[paramName as ToolParamName]}</${paramName}>`).join('\n')
return `\
<${toolName}>${!params ? '' : `\n${params}`}
@@ -234,12 +234,12 @@ const systemToolsXMLPrompt = (chatMode: ChatMode) => {
const toolXMLDefinitions = (`\
Available tools:
${availableXMLToolsStr(tools)}`)
${toolCallDefinitionsXMLString(tools)}`)
const toolCallXMLGuidelines = (`\
Tool calling details:
- Once you write a tool call, you must STOP and WAIT for the result.
- To call a tool, write its name and parameters in one of the XML formats specified above at the BOTTOM of your response.
- To call a tool, write its name and parameters in one of the XML formats specified above.
- After you write the tool call, you must STOP and WAIT for the result.
- All parameters are REQUIRED unless noted otherwise.
- You are only allowed to output ONE tool call, and it must be at the END of your response.
- Your tool call will be executed immediately, and the results will appear in the following user message.`)
@@ -293,8 +293,8 @@ ${directoryStr}
if (mode === 'agent' || mode === 'gather') {
details.push(`Only call tools if they help you accomplish the user's goal. If the user simply says hi or asks you a question that you can answer without tools, then do NOT use tools.`)
details.push('Only use ONE tool call at a time, and always wait for the result before proceeding.') // XML
details.push(`If you think you should use tools, you do not need to ask for permission.`)
details.push('Only use ONE tool call at a time.')
details.push(`NEVER say something like "I'm going to use \`tool_name\`". Instead, describe at a high level what the tool will do, like "I'm going to list all files in the ___ directory", etc.`)
details.push(`Many tools only work if the user has a workspace open.`)
}
@@ -341,9 +341,9 @@ ${details.map((d, i) => `${i + 1}. ${d}`).join('\n\n')}`)
const ansStrs: string[] = []
ansStrs.push(header)
ansStrs.push(sysInfo)
ansStrs.push(fsInfo)
if (toolDefinitions) ansStrs.push(toolDefinitions)
ansStrs.push(importantDetails)
ansStrs.push(fsInfo)
const fullSystemMsgStr = ansStrs
.join('\n\n\n')
@@ -17,7 +17,7 @@ export type ShallowDirectoryItem = {
const toolNamesWithApproval = ['create_file_or_folder', 'delete_file_or_folder', 'edit_file', 'run_terminal_command'] as const satisfies readonly ToolName[]
const toolNamesWithApproval = ['create_file_or_folder', 'delete_file_or_folder', 'edit_file', 'command_tool'] as const satisfies readonly ToolName[]
export type ToolNameWithApproval = typeof toolNamesWithApproval[number]
export const toolNamesThatRequireApproval = new Set<ToolName>(toolNamesWithApproval)
@@ -33,7 +33,7 @@ export type ToolCallParams = {
'edit_file': { uri: URI, changeDescription: string },
'create_file_or_folder': { uri: URI, isFolder: boolean },
'delete_file_or_folder': { uri: URI, isRecursive: boolean, isFolder: boolean },
'run_terminal_command': { command: string, proposedTerminalId: string, waitForCompletion: boolean },
'command_tool': { command: string, proposedTerminalId: string, waitForCompletion: boolean },
}
@@ -49,6 +49,6 @@ export type ToolResultType = {
'edit_file': Promise<{ lintErrors: LintErrorItem[] | null }>,
'create_file_or_folder': {},
'delete_file_or_folder': {},
'run_terminal_command': { terminalId: string, didCreateTerminal: boolean, result: string; resolveReason: TerminalResolveReason; },
'command_tool': { terminalId: string, didCreateTerminal: boolean, result: string; resolveReason: TerminalResolveReason; },
}
@@ -245,6 +245,17 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService {
}
// the stored data structure might be outdated, so we need to update it here
readS = {
...readS,
settingsOfProvider: {
...defaultSettingsOfProvider,
...readS.settingsOfProvider,
mistral: { // we added mistral
...defaultSettingsOfProvider.mistral,
...readS.settingsOfProvider.mistral,
},
} // we added mistral
}
this.state = readS
this.state = _stateWithUpdatedDefaultModels(this.state)
this.state = _validatedModelState(this.state);
@@ -70,7 +70,8 @@ export const displayInfoOfProviderName = (providerName: ProviderName): DisplayIn
}
else if (providerName === 'deepseek') {
return {
title: 'DeepSeek.com API',
// title: 'DeepSeek.com API',
title: 'DeepSeek',
}
}
else if (providerName === 'openRouter') {
@@ -95,22 +96,26 @@ export const displayInfoOfProviderName = (providerName: ProviderName): DisplayIn
}
else if (providerName === 'gemini') {
return {
title: 'Gemini API',
// title: 'Gemini API',
title: 'Gemini',
}
}
else if (providerName === 'groq') {
return {
title: 'Groq.com API',
// title: 'Groq.com API',
title: 'Groq',
}
}
else if (providerName === 'xAI') {
return {
title: 'Grok (xAI)',
// title: 'Grok (xAI)',
title: 'xAI',
}
}
else if (providerName === 'mistral') {
return {
title: 'Mistral API',
// title: 'Mistral API',
title: 'Mistral',
}
}
@@ -3,12 +3,14 @@
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
// disable foreign import complaints
/* eslint-disable */
import Anthropic from '@anthropic-ai/sdk';
import { Ollama } from 'ollama';
import OpenAI, { ClientOptions } from 'openai';
import { MistralCore } from '@mistralai/mistralai/core.js';
import { fimComplete } from '@mistralai/mistralai/funcs/fimComplete.js';
/* eslint-enable */
import { AnthropicLLMChatMessage, LLMChatMessage, LLMFIMMessage, ModelListParams, OllamaModelResponse, OnError, OnFinalMessage, OnText, RawToolCallObj, RawToolParamsObj } from '../../common/sendLLMMessageTypes.js';
import { ChatMode, displayInfoOfProviderName, ModelSelectionOptions, ProviderName, SettingsOfProvider } from '../../common/voidSettingsTypes.js';
@@ -192,7 +194,9 @@ const _sendOpenAICompatibleChat = ({ messages, onText, onFinalMessage, onError,
// tools
const potentialTools = chatMode !== null ? openAITools(chatMode) : null
const nativeToolsObj = potentialTools ? { tools: potentialTools } as const : {}
const nativeToolsObj = potentialTools && specialToolFormat === 'openai-style' ?
{ tools: potentialTools } as const
: {}
// instance
const openai: OpenAI = newOpenAICompatibleSDK({ providerName, settingsOfProvider, includeInPayload })
@@ -374,7 +378,9 @@ const sendAnthropicChat = ({ messages, providerName, onText, onFinalMessage, onE
// tools
const potentialTools = chatMode !== null ? anthropicTools(chatMode) : null
const nativeToolsObj = potentialTools ? { tools: potentialTools, tool_choice: { type: 'auto' } } as const : {}
const nativeToolsObj = potentialTools && specialToolFormat === 'anthropic-style' ?
{ tools: potentialTools, tool_choice: { type: 'auto' } } as const
: {}
// instance
@@ -23,6 +23,7 @@ export class VoidMainUpdateService extends Disposable implements IVoidUpdateServ
super()
}
async check(explicit: boolean): Promise<VoidCheckUpdateRespose> {
const isDevMode = !this._envMainService.isBuilt // found in abstractUpdateService.ts
@@ -37,17 +38,17 @@ export class VoidMainUpdateService extends Disposable implements IVoidUpdateServ
if (this._updateService.state.type === StateType.Uninitialized) {
// The update service hasn't been initialized yet
return { message: explicit ? 'Not yet checking for updates...' : null, action: explicit ? 'reinstall' : undefined } as const
return { message: explicit ? 'Checking for updates soon...' : null, action: explicit ? 'reinstall' : undefined } as const
}
if (this._updateService.state.type === StateType.Idle) {
// No updates currently available
return { message: explicit ? 'No update found!' : null, action: explicit ? 'reinstall' : undefined } as const
return { message: explicit ? 'No updates found!' : null, action: explicit ? 'reinstall' : undefined } as const
}
if (this._updateService.state.type === StateType.CheckingForUpdates) {
// Currently checking for updates
return { message: explicit ? 'No updates found!' : null } as const
return { message: explicit ? 'Checking for updates...' : null } as const
}
if (this._updateService.state.type === StateType.AvailableForDownload) {
@@ -62,7 +63,7 @@ export class VoidMainUpdateService extends Disposable implements IVoidUpdateServ
if (this._updateService.state.type === StateType.Downloaded) {
// Update has been downloaded but not yet ready
return { message: explicit ? 'Got download, need to apply...' : null, action: 'apply' } as const
return { message: explicit ? 'An update is ready to be applied!' : null, action: 'apply' } as const
}
if (this._updateService.state.type === StateType.Updating) {
@@ -76,28 +77,69 @@ export class VoidMainUpdateService extends Disposable implements IVoidUpdateServ
}
if (this._updateService.state.type === StateType.Disabled) {
try {
const res = await fetch(`https://updates.voideditor.dev/api/v0/${this._productService.commit}`)
const resJSON = await res.json()
return await this._manualCheckGHTagIfDisabled(explicit)
}
return null
}
if (!resJSON) return null // null means error
const { hasUpdate, downloadMessage } = resJSON ?? {}
if (hasUpdate === undefined)
return null
const after = (downloadMessage || '') + ''
if (hasUpdate)
return { message: after, action: 'reinstall' } as const
return { message: 'Void is up-to-date!' } as const
private async _manualCheckGHTagIfDisabled(explicit: boolean): Promise<VoidCheckUpdateRespose> {
try {
const response = await fetch('https://api.github.com/repos/voideditor/binaries/releases/latest');
const data = await response.json();
const version = data.tag_name;
const myVersion = `${this._productService.voidVersion}.${this._productService.release}`
const latestVersion = version
const isUpToDate = myVersion === latestVersion // only makes sense if response.ok
let message: string | null
let action: 'reinstall' | undefined
// explicit
if (explicit) {
if (response.ok) {
if (!isUpToDate) {
message = 'A new version of Void is available! Please reinstall (auto-updates are disabled on this OS) - it only takes a second!'
action = 'reinstall'
}
else {
message = 'Void is up-to-date!'
}
}
else {
message = `An error occurred when fetching the latest GitHub release tag. Please try again in ~5 minutes, or reinstall.`
action = 'reinstall'
}
}
catch (e) {
return null
// not explicit
else {
if (response.ok && !isUpToDate) {
message = 'A new version of Void is available! Please reinstall (auto-updates are disabled on this OS) - it only takes a second!'
action = 'reinstall'
}
else {
message = null
}
}
return { message, action } as const
}
catch (e) {
if (explicit) {
return {
message: `An error occurred when fetching the latest GitHub release tag: ${e}. Please try again in ~5 minutes.`,
action: 'reinstall',
}
}
else {
return { message: null } as const
}
}
return null
}
}