mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
Merge pull request #110 from voideditor/ui-refactor-10-15
Settings update
This commit is contained in:
commit
efa3b033af
17 changed files with 1185 additions and 750 deletions
348
extensions/void/package-lock.json
generated
348
extensions/void/package-lock.json
generated
|
|
@ -26,16 +26,19 @@
|
|||
"@vscode/test-cli": "^0.0.10",
|
||||
"@vscode/test-electron": "2.4.1",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"diff-match-patch": "^1.0.5",
|
||||
"esbuild": "^0.23.1",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-react": "^7.35.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"globals": "^15.9.0",
|
||||
"marked": "^14.1.0",
|
||||
"ollama": "^0.5.9",
|
||||
"postcss": "^8.4.41",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-syntax-highlighter": "^15.6.1",
|
||||
"rimraf": "^6.0.1",
|
||||
"tailwindcss": "^3.4.10",
|
||||
"typescript": "5.5.4",
|
||||
|
|
@ -197,6 +200,19 @@
|
|||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.25.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.7.tgz",
|
||||
"integrity": "sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@bcoe/v8-coverage": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
|
||||
|
|
@ -2151,6 +2167,13 @@
|
|||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/diff-match-patch": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz",
|
||||
"integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/diff-sequences": {
|
||||
"version": "29.6.3",
|
||||
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
|
||||
|
|
@ -2843,6 +2866,20 @@
|
|||
"reusify": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/fault": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz",
|
||||
"integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"format": "^0.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/file-entry-cache": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
|
||||
|
|
@ -3028,6 +3065,15 @@
|
|||
"integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/format": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz",
|
||||
"integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.4.x"
|
||||
}
|
||||
},
|
||||
"node_modules/formdata-node": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz",
|
||||
|
|
@ -3328,6 +3374,17 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-parse-selector": {
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz",
|
||||
"integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-to-jsx-runtime": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.0.tgz",
|
||||
|
|
@ -3370,6 +3427,77 @@
|
|||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hastscript": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz",
|
||||
"integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^2.0.0",
|
||||
"comma-separated-tokens": "^1.0.0",
|
||||
"hast-util-parse-selector": "^2.0.0",
|
||||
"property-information": "^5.0.0",
|
||||
"space-separated-tokens": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hastscript/node_modules/@types/hast": {
|
||||
"version": "2.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz",
|
||||
"integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/unist": "^2"
|
||||
}
|
||||
},
|
||||
"node_modules/hastscript/node_modules/@types/unist": {
|
||||
"version": "2.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
|
||||
"integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/hastscript/node_modules/comma-separated-tokens": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz",
|
||||
"integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/hastscript/node_modules/property-information": {
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz",
|
||||
"integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"xtend": "^4.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/hastscript/node_modules/space-separated-tokens": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz",
|
||||
"integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/he": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||
|
|
@ -3380,6 +3508,23 @@
|
|||
"he": "bin/he"
|
||||
}
|
||||
},
|
||||
"node_modules/highlight.js": {
|
||||
"version": "10.7.3",
|
||||
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
|
||||
"integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/highlightjs-vue": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz",
|
||||
"integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==",
|
||||
"dev": true,
|
||||
"license": "CC0-1.0"
|
||||
},
|
||||
"node_modules/html-escaper": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||
|
|
@ -4406,6 +4551,21 @@
|
|||
"loose-envify": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/lowlight": {
|
||||
"version": "1.20.0",
|
||||
"resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz",
|
||||
"integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fault": "^1.0.0",
|
||||
"highlight.js": "~10.7.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "10.4.3",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
|
||||
|
|
@ -5557,6 +5717,16 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/ollama": {
|
||||
"version": "0.5.9",
|
||||
"resolved": "https://registry.npmjs.org/ollama/-/ollama-0.5.9.tgz",
|
||||
"integrity": "sha512-F/KZuDRC+ZsVCuMvcOYuQ6zj42/idzCkkuknGyyGVmNStMZ/sU3jQpvhnl4SyC0+zBzLiKNZJnJeuPFuieWZvQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"whatwg-fetch": "^3.6.20"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
|
|
@ -6173,6 +6343,16 @@
|
|||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/prismjs": {
|
||||
"version": "1.29.0",
|
||||
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
|
||||
"integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
|
|
@ -6311,6 +6491,24 @@
|
|||
"react": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/react-syntax-highlighter": {
|
||||
"version": "15.6.1",
|
||||
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.6.1.tgz",
|
||||
"integrity": "sha512-OqJ2/vL7lEeV5zTJyG7kmARppUjiB9h9udl4qHQjjgEos66z00Ia0OckwYfRxCSFrW8RJIBnsBwQsHZbVPspqg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"highlight.js": "^10.4.1",
|
||||
"highlightjs-vue": "^1.0.0",
|
||||
"lowlight": "^1.17.0",
|
||||
"prismjs": "^1.27.0",
|
||||
"refractor": "^3.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 0.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/read-cache": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
|
|
@ -6372,6 +6570,139 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/refractor": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz",
|
||||
"integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"hastscript": "^6.0.0",
|
||||
"parse-entities": "^2.0.0",
|
||||
"prismjs": "~1.27.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/refractor/node_modules/character-entities": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz",
|
||||
"integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/refractor/node_modules/character-entities-legacy": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz",
|
||||
"integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/refractor/node_modules/character-reference-invalid": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz",
|
||||
"integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/refractor/node_modules/is-alphabetical": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz",
|
||||
"integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/refractor/node_modules/is-alphanumerical": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz",
|
||||
"integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-alphabetical": "^1.0.0",
|
||||
"is-decimal": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/refractor/node_modules/is-decimal": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz",
|
||||
"integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/refractor/node_modules/is-hexadecimal": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz",
|
||||
"integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/refractor/node_modules/parse-entities": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz",
|
||||
"integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"character-entities": "^1.0.0",
|
||||
"character-entities-legacy": "^1.0.0",
|
||||
"character-reference-invalid": "^1.0.0",
|
||||
"is-alphanumerical": "^1.0.0",
|
||||
"is-decimal": "^1.0.0",
|
||||
"is-hexadecimal": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/refractor/node_modules/prismjs": {
|
||||
"version": "1.27.0",
|
||||
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz",
|
||||
"integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.14.1",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/regexp.prototype.flags": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz",
|
||||
|
|
@ -7769,6 +8100,13 @@
|
|||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/whatwg-fetch": {
|
||||
"version": "3.6.20",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz",
|
||||
"integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
|
|
@ -8010,6 +8348,16 @@
|
|||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
|
|
|
|||
|
|
@ -15,254 +15,7 @@
|
|||
"contributes": {
|
||||
"configuration": {
|
||||
"title": "Void",
|
||||
"properties": {
|
||||
"void.whichApi": {
|
||||
"type": "string",
|
||||
"default": "anthropic",
|
||||
"description": "Choose an API provider.",
|
||||
"enum": [
|
||||
"openAI",
|
||||
"openRouter",
|
||||
"openAICompatible",
|
||||
"anthropic",
|
||||
"azure",
|
||||
"ollama",
|
||||
"greptile"
|
||||
]
|
||||
},
|
||||
"void.anthropic.apiKey": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "Anthropic API key."
|
||||
},
|
||||
"void.anthropic.model": {
|
||||
"type": "string",
|
||||
"default": "claude-3-5-sonnet-20240620",
|
||||
"description": "Anthropic model to use.",
|
||||
"enum": [
|
||||
"claude-3-5-sonnet-20240620",
|
||||
"claude-3-opus-20240229",
|
||||
"claude-3-sonnet-20240229",
|
||||
"claude-3-haiku-20240307"
|
||||
]
|
||||
},
|
||||
"void.anthropic.maxTokens": {
|
||||
"type": "string",
|
||||
"default": "8192",
|
||||
"description": "Anthropic max number of tokens to output.",
|
||||
"enum": [
|
||||
"1024",
|
||||
"2048",
|
||||
"4096",
|
||||
"8192"
|
||||
]
|
||||
},
|
||||
"void.openAI.apiKey": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "OpenAI API key."
|
||||
},
|
||||
"void.openAI.model": {
|
||||
"type": "string",
|
||||
"default": "gpt-4o",
|
||||
"description": "OpenAI model to use.",
|
||||
"enum": [
|
||||
"o1-preview",
|
||||
"o1-mini",
|
||||
"gpt-4o",
|
||||
"gpt-4o-2024-05-13",
|
||||
"gpt-4o-2024-08-06",
|
||||
"gpt-4o-mini",
|
||||
"gpt-4o-mini-2024-07-18",
|
||||
"gpt-4-turbo",
|
||||
"gpt-4-turbo-2024-04-09",
|
||||
"gpt-4-turbo-preview",
|
||||
"gpt-4-0125-preview",
|
||||
"gpt-4-1106-preview",
|
||||
"gpt-4",
|
||||
"gpt-4-0613",
|
||||
"gpt-3.5-turbo-0125",
|
||||
"gpt-3.5-turbo",
|
||||
"gpt-3.5-turbo-1106"
|
||||
]
|
||||
},
|
||||
"void.greptile.apiKey": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "Greptile API key."
|
||||
},
|
||||
"void.greptile.githubPAT": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "Github PAT given to Greptile to access your repository."
|
||||
},
|
||||
"void.greptile.remote": {
|
||||
"type": "string",
|
||||
"description": "remote provider",
|
||||
"enum": [
|
||||
"github",
|
||||
"gitlab"
|
||||
]
|
||||
},
|
||||
"void.greptile.repository": {
|
||||
"type": "string",
|
||||
"description": "Repository identifier in \"owner/repository\" format."
|
||||
},
|
||||
"void.greptile.branch": {
|
||||
"type": "string",
|
||||
"default": "main",
|
||||
"description": "Name of the git branch."
|
||||
},
|
||||
"void.azure.apiKey": {
|
||||
"type": "string",
|
||||
"description": "Azure API key."
|
||||
},
|
||||
"void.azure.deploymentId": {
|
||||
"type": "string",
|
||||
"description": "Azure API deployment ID."
|
||||
},
|
||||
"void.azure.resourceName": {
|
||||
"type": "string",
|
||||
"description": "Name of the Azure OpenAI resource. Either this or `baseURL` can be used. \nThe resource name is used in the assembled URL: `https://{resourceName}.openai.azure.com/openai/deployments/{modelId}{path}`"
|
||||
},
|
||||
"void.azure.providerSettings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"baseURL": {
|
||||
"type": "string",
|
||||
"default": "https://${resourceName}.openai.azure.com/openai/deployments",
|
||||
"description": "Azure API base URL."
|
||||
},
|
||||
"headers": {
|
||||
"type": "object",
|
||||
"description": "Custom headers to include in the requests."
|
||||
}
|
||||
}
|
||||
},
|
||||
"void.ollama.endpoint": {
|
||||
"type": "string",
|
||||
"default": "http://127.0.0.1:11434",
|
||||
"description": "The Ollama endpoint. Start Ollama by running `OLLAMA_ORIGINS=\"vscode-webview://*\" ollama serve`"
|
||||
},
|
||||
"void.ollama.model": {
|
||||
"type": "string",
|
||||
"default": "llama3.1",
|
||||
"description": "Ollama model to use.",
|
||||
"enum": [
|
||||
"codegemma",
|
||||
"codegemma:2b",
|
||||
"codegemma:7b",
|
||||
"codellama",
|
||||
"codellama:7b",
|
||||
"codellama:13b",
|
||||
"codellama:34b",
|
||||
"codellama:70b",
|
||||
"codellama:code",
|
||||
"codellama:python",
|
||||
"command-r",
|
||||
"command-r:35b",
|
||||
"command-r-plus",
|
||||
"command-r-plus:104b",
|
||||
"deepseek-coder-v2",
|
||||
"deepseek-coder-v2:16b",
|
||||
"deepseek-coder-v2:236b",
|
||||
"falcon2",
|
||||
"falcon2:11b",
|
||||
"firefunction-v2",
|
||||
"firefunction-v2:70b",
|
||||
"gemma",
|
||||
"gemma:2b",
|
||||
"gemma:7b",
|
||||
"gemma2",
|
||||
"gemma2:2b",
|
||||
"gemma2:9b",
|
||||
"gemma2:27b",
|
||||
"llama2",
|
||||
"llama2:7b",
|
||||
"llama2:13b",
|
||||
"llama2:70b",
|
||||
"llama3",
|
||||
"llama3:8b",
|
||||
"llama3:70b",
|
||||
"llama3-chatqa",
|
||||
"llama3-chatqa:8b",
|
||||
"llama3-chatqa:70b",
|
||||
"llama3-gradient",
|
||||
"llama3-gradient:8b",
|
||||
"llama3-gradient:70b",
|
||||
"llama3.1",
|
||||
"llama3.1:8b",
|
||||
"llama3.1:70b",
|
||||
"llama3.1:405b",
|
||||
"llava",
|
||||
"llava:7b",
|
||||
"llava:13b",
|
||||
"llava:34b",
|
||||
"llava-llama3",
|
||||
"llava-llama3:8b",
|
||||
"llava-phi3",
|
||||
"llava-phi3:3.8b",
|
||||
"mistral",
|
||||
"mistral:7b",
|
||||
"mistral-large",
|
||||
"mistral-large:123b",
|
||||
"mistral-nemo",
|
||||
"mistral-nemo:12b",
|
||||
"mixtral",
|
||||
"mixtral:8x7b",
|
||||
"mixtral:8x22b",
|
||||
"moondream",
|
||||
"moondream:1.8b",
|
||||
"openhermes",
|
||||
"openhermes:v2.5",
|
||||
"phi3",
|
||||
"phi3:3.8b",
|
||||
"phi3:14b",
|
||||
"phi3.5",
|
||||
"phi3.5:3.8b",
|
||||
"qwen",
|
||||
"qwen:7b",
|
||||
"qwen:14b",
|
||||
"qwen:32b",
|
||||
"qwen:72b",
|
||||
"qwen:110b",
|
||||
"qwen2",
|
||||
"qwen2:0.5b",
|
||||
"qwen2:1.5b",
|
||||
"qwen2:7b",
|
||||
"qwen2:72b",
|
||||
"smollm",
|
||||
"smollm:135m",
|
||||
"smollm:360m",
|
||||
"smollm:1.7b"
|
||||
]
|
||||
},
|
||||
"void.openRouter.model": {
|
||||
"type": "string",
|
||||
"default": "openai/gpt-4o",
|
||||
"description": "OpenRouter model to use."
|
||||
},
|
||||
"void.openRouter.apiKey": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "OpenRouter API key."
|
||||
},
|
||||
"void.openAICompatible.endpoint": {
|
||||
"type": "string",
|
||||
"default": "http://127.0.0.1:11434/v1",
|
||||
"description": "The endpoint."
|
||||
},
|
||||
"void.openAICompatible.model": {
|
||||
"type": "string",
|
||||
"default": "gpt-4o",
|
||||
"description": "The name of the model to use."
|
||||
},
|
||||
"void.openAICompatible.apiKey": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "Your API key."
|
||||
}
|
||||
}
|
||||
"properties": {}
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
|
|
@ -292,7 +45,7 @@
|
|||
"icon": "$(history)"
|
||||
},
|
||||
{
|
||||
"command": "void.openSettings",
|
||||
"command": "void.toggleSettings",
|
||||
"title": "Void settings",
|
||||
"icon": "$(settings-gear)"
|
||||
}
|
||||
|
|
@ -340,7 +93,7 @@
|
|||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "void.openSettings",
|
||||
"command": "void.toggleSettings",
|
||||
"when": "view == 'void.viewnumberone'",
|
||||
"group": "navigation"
|
||||
}
|
||||
|
|
@ -369,16 +122,19 @@
|
|||
"@vscode/test-cli": "^0.0.10",
|
||||
"@vscode/test-electron": "2.4.1",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"diff-match-patch": "^1.0.5",
|
||||
"esbuild": "^0.23.1",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-react": "^7.35.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"globals": "^15.9.0",
|
||||
"marked": "^14.1.0",
|
||||
"ollama": "^0.5.9",
|
||||
"postcss": "^8.4.41",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-syntax-highlighter": "^15.6.1",
|
||||
"rimraf": "^6.0.1",
|
||||
"tailwindcss": "^3.4.10",
|
||||
"typescript": "5.5.4",
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export class DisplayChangesProvider implements vscode.CodeLensProvider {
|
|||
// used internally by vscode
|
||||
public provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): vscode.ProviderResult<vscode.CodeLens[]> {
|
||||
const docUriStr = document.uri.toString()
|
||||
return this._diffsOfDocument[docUriStr].flatMap(diff => diff.lenses)
|
||||
return this._diffsOfDocument[docUriStr]?.flatMap(diff => diff.lenses) ?? []
|
||||
}
|
||||
|
||||
// declared by us, registered with vscode.languages.registerCodeLensProvider()
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export class SidebarWebviewProvider implements vscode.WebviewViewProvider {
|
|||
|
||||
private readonly _extensionUri: vscode.Uri
|
||||
|
||||
private _webviewView?: vscode.WebviewView; // only used inside onDidChangeConfiguration
|
||||
// private _webviewView?: vscode.WebviewView;
|
||||
private _webviewDeps: string[] = [];
|
||||
|
||||
constructor(context: vscode.ExtensionContext) {
|
||||
|
|
@ -28,35 +28,14 @@ export class SidebarWebviewProvider implements vscode.WebviewViewProvider {
|
|||
|
||||
let temp_res: typeof this._res | undefined = undefined
|
||||
this.webview = new Promise((res, rej) => { temp_res = res })
|
||||
if (!temp_res) throw new Error("sidebar provider: resolver was undefined")
|
||||
if (!temp_res) throw new Error("Void sidebar provider: resolver was undefined")
|
||||
this._res = temp_res
|
||||
|
||||
// if it affects one of the config items webview depends on, update the webview
|
||||
// TODO should be able to move this entirely to React - make updateWebviewHTML mount once, and then send updates via postMessage from then on
|
||||
vscode.workspace.onDidChangeConfiguration(event => {
|
||||
if (this._webviewDeps.map(dep => event.affectsConfiguration(dep)).some(v => !!v)) {
|
||||
if (this._webviewView) {
|
||||
this.updateWebviewHTML(this._webviewView.webview);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// this is updated
|
||||
private updateWebviewHTML(webview: vscode.Webview) {
|
||||
const allowed_urls = ['https://api.anthropic.com', 'https://api.openai.com', 'https://api.greptile.com'];
|
||||
// called by us
|
||||
updateWebviewHTML(webview: vscode.Webview) {
|
||||
this._webviewDeps = []
|
||||
|
||||
const ollamaEndpoint: string | undefined = vscode.workspace.getConfiguration('void.ollama').get('endpoint');
|
||||
this._webviewDeps.push('void.ollama.endpoint');
|
||||
if (ollamaEndpoint)
|
||||
allowed_urls.push(ollamaEndpoint);
|
||||
|
||||
const openAICompatibleEndpoint: string | undefined = vscode.workspace.getConfiguration('void.openAICompatible').get('endpoint');
|
||||
this._webviewDeps.push('void.openAICompatible.endpoint');
|
||||
if (openAICompatibleEndpoint)
|
||||
allowed_urls.push(openAICompatibleEndpoint+'/chat/completions');
|
||||
|
||||
const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'dist/sidebar/index.js'));
|
||||
const stylesUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'dist/sidebar/styles.css'));
|
||||
const rootUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri));
|
||||
|
|
@ -68,7 +47,7 @@ export class SidebarWebviewProvider implements vscode.WebviewViewProvider {
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Custom View</title>
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; connect-src ${allowed_urls.join(' ')}; img-src vscode-resource: https:; script-src 'nonce-${nonce}'; style-src vscode-resource: 'unsafe-inline' http: https: data:;">
|
||||
<meta http-equiv="Content-Security-Policy" content="img-src vscode-resource: https:; script-src 'nonce-${nonce}'; style-src vscode-resource: 'unsafe-inline' http: https: data:;">
|
||||
<base href="${rootUri}/">
|
||||
<link href="${stylesUri}" rel="stylesheet">
|
||||
</head>
|
||||
|
|
@ -100,6 +79,6 @@ export class SidebarWebviewProvider implements vscode.WebviewViewProvider {
|
|||
|
||||
// resolve webview and _webviewView
|
||||
this._res(webview);
|
||||
this._webviewView = webviewView;
|
||||
// this._webviewView = webviewView;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,436 +1,295 @@
|
|||
import Anthropic from '@anthropic-ai/sdk'
|
||||
import Anthropic from '@anthropic-ai/sdk';
|
||||
import OpenAI from 'openai';
|
||||
import { Ollama } from 'ollama/browser'
|
||||
import OpenAI from 'openai'
|
||||
import { VoidConfig } from '../sidebar/contextForConfig';
|
||||
|
||||
// always compare these against package.json to make sure every setting in this type can actually be provided by the user
|
||||
export type ApiConfig = {
|
||||
anthropic: {
|
||||
apikey: string,
|
||||
model: string,
|
||||
maxTokens: string
|
||||
},
|
||||
openAI: {
|
||||
apikey: string,
|
||||
model: string
|
||||
},
|
||||
greptile: {
|
||||
apikey: string,
|
||||
githubPAT: string,
|
||||
repoinfo: {
|
||||
remote: string, // e.g. 'github'
|
||||
repository: string, // e.g. 'voideditor/void'
|
||||
branch: string // e.g. 'main'
|
||||
}
|
||||
},
|
||||
ollama: {
|
||||
endpoint: string,
|
||||
model: string
|
||||
},
|
||||
openAICompatible: {
|
||||
endpoint: string,
|
||||
model: string,
|
||||
apikey: string
|
||||
},
|
||||
openRouter: {
|
||||
model: string,
|
||||
apikey: string
|
||||
}
|
||||
whichApi: string
|
||||
}
|
||||
|
||||
type OnText = (newText: string, fullText: string) => void;
|
||||
|
||||
|
||||
type OnText = (newText: string, fullText: string) => void
|
||||
|
||||
export type LLMMessage = {
|
||||
role: 'user' | 'assistant',
|
||||
content: string
|
||||
};
|
||||
}
|
||||
|
||||
type SendLLMMessageFnTypeInternal = (params: {
|
||||
messages: LLMMessage[],
|
||||
onText: OnText,
|
||||
onFinalMessage: (input: string) => void,
|
||||
onError: (message: string) => void,
|
||||
apiConfig: ApiConfig
|
||||
}) => {
|
||||
abort: () => void
|
||||
};
|
||||
onError: (error: string) => void,
|
||||
voidConfig: VoidConfig,
|
||||
})
|
||||
=> {
|
||||
abort: () => void
|
||||
}
|
||||
|
||||
type SendLLMMessageFnTypeExternal = (params: {
|
||||
messages: LLMMessage[],
|
||||
onText: OnText,
|
||||
onFinalMessage: (input: string) => void,
|
||||
onError: (message: string) => void,
|
||||
apiConfig: ApiConfig | null
|
||||
}) => {
|
||||
abort: () => void
|
||||
};
|
||||
|
||||
type AnthropicErrorResponse = {
|
||||
type: string,
|
||||
error: {
|
||||
type: string,
|
||||
message: string
|
||||
};
|
||||
};
|
||||
|
||||
// Helper function to handle missing API keys
|
||||
const handleMissingApiKey = (serviceName: string, onError: (message: string) => void) => {
|
||||
onError(`${serviceName} API key not set`);
|
||||
return { abort: () => {} }
|
||||
};
|
||||
|
||||
// Claude
|
||||
const sendClaudeMsg: SendLLMMessageFnTypeInternal = ({
|
||||
messages,
|
||||
onText,
|
||||
onFinalMessage,
|
||||
onError,
|
||||
apiConfig
|
||||
}) => {
|
||||
const { apikey, model, maxTokens } = apiConfig.anthropic;
|
||||
|
||||
if (!apikey) {
|
||||
return handleMissingApiKey('Anthropic', onError);
|
||||
onError: (error: string) => void,
|
||||
voidConfig: VoidConfig | null,
|
||||
})
|
||||
=> {
|
||||
abort: () => void
|
||||
}
|
||||
|
||||
let didAbort = false;
|
||||
|
||||
const anthropic = new Anthropic({
|
||||
apiKey: apikey,
|
||||
dangerouslyAllowBrowser: true,
|
||||
|
||||
|
||||
// Anthropic
|
||||
const sendAnthropicMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig }) => {
|
||||
|
||||
const anthropic = new Anthropic({ apiKey: voidConfig.anthropic.apikey, dangerouslyAllowBrowser: true }); // defaults to process.env["ANTHROPIC_API_KEY"]
|
||||
|
||||
const stream = anthropic.messages.stream({
|
||||
model: voidConfig.anthropic.model,
|
||||
max_tokens: parseInt(voidConfig.anthropic.maxTokens),
|
||||
messages: messages,
|
||||
});
|
||||
|
||||
let did_abort = false
|
||||
|
||||
// when receive text
|
||||
stream.on('text', (newText, fullText) => {
|
||||
if (did_abort) return
|
||||
onText(newText, fullText)
|
||||
})
|
||||
|
||||
const stream = anthropic.messages
|
||||
.stream({
|
||||
model: model,
|
||||
max_tokens: parseInt(maxTokens),
|
||||
messages: messages,
|
||||
stream: true
|
||||
})
|
||||
.on('error', (err) => {
|
||||
if (err instanceof Anthropic.APIError) {
|
||||
if (err.status === 401) {
|
||||
onError('Unauthorized: Invalid Anthropic API key');
|
||||
} else {
|
||||
onError((err.error as AnthropicErrorResponse).error.message);
|
||||
}
|
||||
} else {
|
||||
console.error(err);
|
||||
onError(err.message);
|
||||
}
|
||||
})
|
||||
.on('text', (newText, fullText) => {
|
||||
if (didAbort) return;
|
||||
onText(newText, fullText);
|
||||
})
|
||||
.on('finalMessage', (claudeResponse) => {
|
||||
if (didAbort) return;
|
||||
const content = claudeResponse.content
|
||||
.filter((c) => c.type === 'text')
|
||||
.map((c) => c.text)
|
||||
.join('\n');
|
||||
onFinalMessage(content);
|
||||
});
|
||||
// when we get the final message on this stream (or when error/fail)
|
||||
stream.on('finalMessage', (claude_response) => {
|
||||
if (did_abort) return
|
||||
// stringify the response's content
|
||||
let content = claude_response.content.map(c => { if (c.type === 'text') { return c.text } }).join('\n');
|
||||
onFinalMessage(content)
|
||||
})
|
||||
|
||||
stream.on('error', (error) => {
|
||||
// the most common error will be invalid API key (401), so we handle this with a nice message
|
||||
if (error instanceof Anthropic.APIError && error.status === 401) {
|
||||
onError('Invalid API key.')
|
||||
}
|
||||
else {
|
||||
onError(error.message)
|
||||
}
|
||||
})
|
||||
|
||||
// if abort is called, onFinalMessage is NOT called, and no later onTexts are called either
|
||||
const abort = () => {
|
||||
stream.controller.abort();
|
||||
didAbort = true;
|
||||
};
|
||||
// stream.controller.abort() // TODO need to test this to make sure it works, it might throw an error
|
||||
did_abort = true
|
||||
}
|
||||
|
||||
return { abort }
|
||||
|
||||
return { abort };
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
// OpenAI, OpenRouter, OpenAICompatible
|
||||
const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({
|
||||
messages,
|
||||
onText,
|
||||
onFinalMessage,
|
||||
onError,
|
||||
apiConfig
|
||||
}) => {
|
||||
const { apikey, model } = apiConfig.openAI;
|
||||
const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig }) => {
|
||||
|
||||
let didAbort = false
|
||||
let fullText = ''
|
||||
|
||||
|
||||
let didAbort = false;
|
||||
let fullText = '';
|
||||
|
||||
let abort = () => {
|
||||
// if abort is called, onFinalMessage is NOT called, and no later onTexts are called either
|
||||
let abort: () => void = () => {
|
||||
didAbort = true;
|
||||
};
|
||||
|
||||
let openai: OpenAI;
|
||||
let options: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming;
|
||||
let openai: OpenAI
|
||||
let options: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming
|
||||
|
||||
|
||||
if (apiConfig.whichApi === 'openAI') {
|
||||
if (!apikey) {
|
||||
return handleMissingApiKey('OpenAI', onError);
|
||||
}
|
||||
openai = new OpenAI({ apiKey: apiConfig.openAI.apikey, dangerouslyAllowBrowser: true });
|
||||
options = { model: apiConfig.openAI.model, messages: messages, stream: true, };
|
||||
if (voidConfig.default.whichApi === 'openAI') {
|
||||
openai = new OpenAI({ apiKey: voidConfig.openAI.apikey, dangerouslyAllowBrowser: true });
|
||||
options = { model: voidConfig.openAI.model, messages: messages, stream: true, }
|
||||
}
|
||||
else if (apiConfig.whichApi === 'openRouter') {
|
||||
else if (voidConfig.default.whichApi === 'openRouter') {
|
||||
openai = new OpenAI({
|
||||
baseURL: "https://openrouter.ai/api/v1", apiKey: apiConfig.openRouter.apikey, dangerouslyAllowBrowser: true,
|
||||
baseURL: "https://openrouter.ai/api/v1", apiKey: voidConfig.openRouter.apikey, dangerouslyAllowBrowser: true,
|
||||
defaultHeaders: {
|
||||
"HTTP-Referer": 'https://voideditor.com', // Optional, for including your app on openrouter.ai rankings.
|
||||
"X-Title": 'Void Editor', // Optional. Shows in rankings on openrouter.ai.
|
||||
},
|
||||
});
|
||||
options = { model: apiConfig.openRouter.model, messages: messages, stream: true, }
|
||||
options = { model: voidConfig.openRouter.model, messages: messages, stream: true, }
|
||||
}
|
||||
else if (apiConfig.whichApi === 'openAICompatible') {
|
||||
openai = new OpenAI({ baseURL: apiConfig.openAICompatible.endpoint, apiKey: apiConfig.openAICompatible.apikey, dangerouslyAllowBrowser: true });
|
||||
options = { model: apiConfig.openAICompatible.model, messages: messages, stream: true, };
|
||||
else if (voidConfig.default.whichApi === 'openAICompatible') {
|
||||
openai = new OpenAI({ baseURL: voidConfig.openAICompatible.endpoint, apiKey: voidConfig.openAICompatible.apikey, dangerouslyAllowBrowser: true })
|
||||
options = { model: voidConfig.openAICompatible.model, messages: messages, stream: true, }
|
||||
}
|
||||
else {
|
||||
onError(`Invalid API: ${apiConfig.whichApi}`);
|
||||
throw new Error(`apiConfig.whichAPI was invalid: ${apiConfig.whichApi}`);
|
||||
console.error(`sendOpenAIMsg: invalid whichApi: ${voidConfig.default.whichApi}`)
|
||||
throw new Error(`voidConfig.whichAPI was invalid: ${voidConfig.default.whichApi}`)
|
||||
}
|
||||
|
||||
openai.chat.completions
|
||||
.create(options)
|
||||
.then(async (response) => {
|
||||
.then(async response => {
|
||||
abort = () => {
|
||||
response.controller.abort();
|
||||
// response.controller.abort()
|
||||
didAbort = true;
|
||||
};
|
||||
try {
|
||||
for await (const chunk of response) {
|
||||
if (didAbort) return;
|
||||
const newText = chunk.choices[0]?.delta?.content || '';
|
||||
fullText += newText;
|
||||
onText(newText, fullText);
|
||||
}
|
||||
// when receive text
|
||||
for await (const chunk of response) {
|
||||
if (didAbort) return;
|
||||
const newText = chunk.choices[0]?.delta?.content || '';
|
||||
fullText += newText;
|
||||
onText(newText, fullText);
|
||||
}
|
||||
onFinalMessage(fullText);
|
||||
})
|
||||
// when error/fail - this catches errors of both .create() and .then(for await)
|
||||
.catch(error => {
|
||||
if (error instanceof OpenAI.APIError) {
|
||||
if (error.status === 401) {
|
||||
onError('Invalid API key.');
|
||||
}
|
||||
if (!didAbort) {
|
||||
onFinalMessage(fullText);
|
||||
}
|
||||
} catch (error) {
|
||||
onError(`Error in stream: ${error}`);
|
||||
console.error('Error in OpenAI stream:', error);
|
||||
if (!didAbort) {
|
||||
onFinalMessage(fullText);
|
||||
else {
|
||||
onError(error.message);
|
||||
}
|
||||
}
|
||||
else {
|
||||
onError(error);
|
||||
}
|
||||
})
|
||||
.catch((responseError) => {
|
||||
if (responseError.status === 401) {
|
||||
onError('Unauthorized: Invalid API key');
|
||||
} else if (responseError.status === 400 && responseError.param === 'stream') {
|
||||
onError(`The model '${model}' does not support streamed responses.`);
|
||||
} else {
|
||||
onError(responseError.message);
|
||||
}
|
||||
});
|
||||
|
||||
return { abort };
|
||||
};
|
||||
|
||||
|
||||
// Ollama
|
||||
const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({
|
||||
messages,
|
||||
onText,
|
||||
onFinalMessage,
|
||||
onError,
|
||||
apiConfig
|
||||
}) => {
|
||||
const { endpoint, model } = apiConfig.ollama;
|
||||
export const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig }) => {
|
||||
|
||||
if (!endpoint) {
|
||||
onError('Ollama endpoint not set');
|
||||
return { abort: () => {} };
|
||||
}
|
||||
|
||||
let didAbort = false;
|
||||
let fullText = '';
|
||||
|
||||
const ollama = new Ollama({ host: endpoint });
|
||||
let didAbort = false
|
||||
let fullText = ""
|
||||
|
||||
// if abort is called, onFinalMessage is NOT called, and no later onTexts are called either
|
||||
let abort = () => {
|
||||
didAbort = true;
|
||||
};
|
||||
|
||||
ollama
|
||||
.chat({
|
||||
model: model,
|
||||
messages: messages,
|
||||
stream: true
|
||||
})
|
||||
.then(async (stream) => {
|
||||
const ollama = new Ollama({ host: voidConfig.ollama.endpoint })
|
||||
|
||||
ollama.chat({
|
||||
model: voidConfig.ollama.model,
|
||||
messages: messages,
|
||||
stream: true,
|
||||
})
|
||||
.then(async stream => {
|
||||
abort = () => {
|
||||
ollama.abort();
|
||||
didAbort = true;
|
||||
};
|
||||
try {
|
||||
for await (const chunk of stream) {
|
||||
if (didAbort) return;
|
||||
const newText = chunk.message.content;
|
||||
fullText += newText;
|
||||
onText(newText, fullText);
|
||||
}
|
||||
if (!didAbort) {
|
||||
onFinalMessage(fullText);
|
||||
}
|
||||
} catch (error) {
|
||||
onError(`Error while streaming response: ${error}`);
|
||||
console.error('Error while streaming response:', error);
|
||||
if (!didAbort) {
|
||||
onFinalMessage(fullText);
|
||||
}
|
||||
// ollama.abort()
|
||||
didAbort = true
|
||||
}
|
||||
// iterate through the stream
|
||||
for await (const chunk of stream) {
|
||||
if (didAbort) return;
|
||||
const newText = chunk.message.content;
|
||||
fullText += newText;
|
||||
onText(newText, fullText);
|
||||
}
|
||||
onFinalMessage(fullText);
|
||||
|
||||
})
|
||||
// when error/fail
|
||||
.catch(error => {
|
||||
onError(error)
|
||||
})
|
||||
.catch((responseError) => {
|
||||
if (responseError.error) {
|
||||
onError(responseError.error.charAt(0).toUpperCase() + responseError.error.slice(1));
|
||||
} else {
|
||||
onError(responseError.message);
|
||||
}
|
||||
console.error(responseError);
|
||||
});
|
||||
|
||||
return { abort };
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Greptile
|
||||
const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({
|
||||
messages,
|
||||
onText,
|
||||
onFinalMessage,
|
||||
onError,
|
||||
apiConfig,
|
||||
}) => {
|
||||
const { apikey, githubPAT, repoinfo } = apiConfig.greptile;
|
||||
// https://docs.greptile.com/api-reference/query
|
||||
// https://docs.greptile.com/quickstart#sample-response-streamed
|
||||
|
||||
if (!apikey) {
|
||||
return handleMissingApiKey('Greptile', onError);
|
||||
}
|
||||
if (!githubPAT) {
|
||||
onError('GitHub token not set');
|
||||
return { abort: () => {} };
|
||||
}
|
||||
const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig }) => {
|
||||
|
||||
let didAbort = false;
|
||||
let fullText = '';
|
||||
let didAbort = false
|
||||
let fullText = ''
|
||||
|
||||
// if abort is called, onFinalMessage is NOT called, and no later onTexts are called either
|
||||
let abort: () => void = () => { didAbort = true }
|
||||
|
||||
const controller = new AbortController();
|
||||
|
||||
fetch('https://api.greptile.com/v2/query', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${apikey}`,
|
||||
'X-Github-Token': `${githubPAT}`,
|
||||
'Content-Type': `application/json`
|
||||
"Authorization": `Bearer ${voidConfig.greptile.apikey}`,
|
||||
"X-Github-Token": `${voidConfig.greptile.githubPAT}`,
|
||||
"Content-Type": `application/json`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
messages,
|
||||
stream: true,
|
||||
repositories: [repoinfo]
|
||||
repositories: [voidConfig.greptile.repoinfo]
|
||||
}),
|
||||
signal: controller.signal
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status === 401) {
|
||||
onError('Unauthorized: Invalid Greptile API key');
|
||||
return null;
|
||||
} else if (response.status !== 200) {
|
||||
onError(`Error: ${response.status} ${response.statusText}`);
|
||||
return null;
|
||||
}
|
||||
return response.body;
|
||||
// this is {message}\n{message}\n{message}...\n
|
||||
.then(async response => {
|
||||
const text = await response.text()
|
||||
console.log('got greptile', text)
|
||||
return JSON.parse(`[${text.trim().split('\n').join(',')}]`)
|
||||
})
|
||||
.then(async (body) => {
|
||||
if (!body || didAbort) return;
|
||||
const reader = body.getReader();
|
||||
const decoder = new TextDecoder('utf-8');
|
||||
while (!didAbort) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done || didAbort) break;
|
||||
const chunk = decoder.decode(value, { stream: true });
|
||||
const messages = chunk.trim().split('\n').filter(Boolean);
|
||||
for (const msg of messages) {
|
||||
try {
|
||||
const parsed = JSON.parse(msg);
|
||||
const { type, message } = parsed;
|
||||
if (type === 'message' || type === 'sources') {
|
||||
fullText += message;
|
||||
onText(message, fullText);
|
||||
} else if (type === 'status' && !message) {
|
||||
if (!didAbort) {
|
||||
onFinalMessage(fullText);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error parsing Greptile response:', e);
|
||||
onError(`Error parsing Greptile response: ${e}`);
|
||||
// TODO make this actually stream, right now it just sends one message at the end
|
||||
.then(async responseArr => {
|
||||
if (didAbort)
|
||||
return
|
||||
|
||||
for (let response of responseArr) {
|
||||
|
||||
const type: string = response['type']
|
||||
const message = response['message']
|
||||
|
||||
// when receive text
|
||||
if (type === 'message') {
|
||||
fullText += message
|
||||
onText(message, fullText)
|
||||
}
|
||||
else if (type === 'sources') {
|
||||
const { filepath, linestart, lineend } = message as { filepath: string, linestart: number | null, lineend: number | null }
|
||||
fullText += filepath
|
||||
onText(filepath, fullText)
|
||||
}
|
||||
// type: 'status' with an empty 'message' means last message
|
||||
else if (type === 'status') {
|
||||
if (!message) {
|
||||
onFinalMessage(fullText)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
.catch((e) => {
|
||||
if (didAbort) return;
|
||||
console.error('Error in Greptile stream:', e);
|
||||
onError(`Error in Greptile stream: ${e}`);
|
||||
if (!didAbort) {
|
||||
onFinalMessage(fullText);
|
||||
}
|
||||
.catch(e => {
|
||||
onError(e)
|
||||
});
|
||||
|
||||
const abort = () => {
|
||||
controller.abort();
|
||||
didAbort = true;
|
||||
};
|
||||
return { abort }
|
||||
|
||||
return { abort };
|
||||
};
|
||||
}
|
||||
|
||||
export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({
|
||||
messages,
|
||||
onText,
|
||||
onFinalMessage,
|
||||
onError,
|
||||
apiConfig,
|
||||
}) => {
|
||||
if (!apiConfig) {
|
||||
onError('API configuration is missing');
|
||||
return { abort: () => {} };
|
||||
}
|
||||
|
||||
switch (apiConfig.whichApi) {
|
||||
|
||||
export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({ messages, onText, onFinalMessage, onError, voidConfig }) => {
|
||||
if (!voidConfig) return { abort: () => { } }
|
||||
|
||||
switch (voidConfig.default.whichApi) {
|
||||
case 'anthropic':
|
||||
|
||||
return sendClaudeMsg({
|
||||
messages,
|
||||
onText,
|
||||
onFinalMessage,
|
||||
onError,
|
||||
apiConfig,
|
||||
});
|
||||
return sendAnthropicMsg({ messages, onText, onFinalMessage, onError, voidConfig });
|
||||
case 'openAI':
|
||||
case 'openRouter':
|
||||
case 'openAICompatible':
|
||||
return sendOpenAIMsg({ messages, onText, onFinalMessage, onError, apiConfig });
|
||||
case 'greptile':
|
||||
return sendGreptileMsg({
|
||||
messages,
|
||||
onText,
|
||||
onFinalMessage,
|
||||
onError,
|
||||
apiConfig
|
||||
});
|
||||
return sendOpenAIMsg({ messages, onText, onFinalMessage, onError, voidConfig });
|
||||
case 'ollama':
|
||||
|
||||
return sendOllamaMsg({
|
||||
messages,
|
||||
onText,
|
||||
onFinalMessage,
|
||||
onError,
|
||||
apiConfig
|
||||
});
|
||||
|
||||
return sendOllamaMsg({ messages, onText, onFinalMessage, onError, voidConfig });
|
||||
case 'greptile':
|
||||
return sendGreptileMsg({ messages, onText, onFinalMessage, onError, voidConfig });
|
||||
default:
|
||||
onError(`Error: whichApi was '${apiConfig.whichApi}', which is not recognized!`);
|
||||
return { abort: () => {} };
|
||||
onError(`Error: whichApi was ${voidConfig.default.whichApi}, which is not recognized!`)
|
||||
return { abort: () => { } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,53 +2,12 @@ import * as vscode from 'vscode';
|
|||
import { DisplayChangesProvider } from './DisplayChangesProvider';
|
||||
import { BaseDiffArea, ChatThreads, MessageFromSidebar, MessageToSidebar } from './shared_types';
|
||||
import { SidebarWebviewProvider } from './SidebarWebviewProvider';
|
||||
import { ApiConfig } from './common/sendLLMMessage';
|
||||
|
||||
const readFileContentOfUri = async (uri: vscode.Uri) => {
|
||||
return Buffer.from(await vscode.workspace.fs.readFile(uri)).toString('utf8')
|
||||
.replace(/\r\n/g, '\n') // replace windows \r\n with \n
|
||||
}
|
||||
|
||||
|
||||
const getApiConfig = () => {
|
||||
const apiConfig: ApiConfig = {
|
||||
anthropic: {
|
||||
apikey: vscode.workspace.getConfiguration('void.anthropic').get('apiKey') ?? '',
|
||||
model: vscode.workspace.getConfiguration('void.anthropic').get('model') ?? '',
|
||||
maxTokens: vscode.workspace.getConfiguration('void.anthropic').get('maxTokens') ?? '',
|
||||
},
|
||||
openAI: {
|
||||
apikey: vscode.workspace.getConfiguration('void.openAI').get('apiKey') ?? '',
|
||||
model: vscode.workspace.getConfiguration('void.openAI').get('model') ?? '',
|
||||
},
|
||||
greptile: {
|
||||
apikey: vscode.workspace.getConfiguration('void.greptile').get('apiKey') ?? '',
|
||||
githubPAT: vscode.workspace.getConfiguration('void.greptile').get('githubPAT') ?? '',
|
||||
repoinfo: {
|
||||
remote: 'github',
|
||||
repository: 'TODO',
|
||||
branch: 'main'
|
||||
}
|
||||
},
|
||||
ollama: {
|
||||
endpoint: vscode.workspace.getConfiguration('void.ollama').get('endpoint') ?? '',
|
||||
model: vscode.workspace.getConfiguration('void.ollama').get('model') ?? '',
|
||||
},
|
||||
openAICompatible: {
|
||||
endpoint: vscode.workspace.getConfiguration('void.openAICompatible').get('endpoint') ?? '',
|
||||
model: vscode.workspace.getConfiguration('void.openAICompatible').get('model') ?? '',
|
||||
apikey: vscode.workspace.getConfiguration('void.openAICompatible').get('apiKey') ?? '',
|
||||
},
|
||||
openRouter: {
|
||||
model: vscode.workspace.getConfiguration('void.openRouter').get('model') ?? '',
|
||||
apikey: vscode.workspace.getConfiguration('void.openRouter').get('apiKey') ?? '',
|
||||
},
|
||||
whichApi: vscode.workspace.getConfiguration('void').get('whichApi') ?? ''
|
||||
}
|
||||
return apiConfig
|
||||
}
|
||||
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
// 1. Mount the chat sidebar
|
||||
|
|
@ -95,10 +54,6 @@ export function activate(context: vscode.ExtensionContext) {
|
|||
displayChangesProvider.rejectDiff(params)
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('void.openSettings', async () => {
|
||||
vscode.commands.executeCommand('workbench.action.openSettings', '@ext:void.void');
|
||||
}));
|
||||
|
||||
// 5. Receive messages from sidebar
|
||||
webviewProvider.webview.then(
|
||||
webview => {
|
||||
|
|
@ -110,15 +65,9 @@ export function activate(context: vscode.ExtensionContext) {
|
|||
context.subscriptions.push(vscode.commands.registerCommand('void.toggleThreadSelector', async () => {
|
||||
webview.postMessage({ type: 'toggleThreadSelector' } satisfies MessageToSidebar)
|
||||
}))
|
||||
|
||||
// when config changes, send it to the sidebar
|
||||
vscode.workspace.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('void')) {
|
||||
const apiConfig = getApiConfig()
|
||||
webview.postMessage({ type: 'apiConfig', apiConfig } satisfies MessageToSidebar)
|
||||
}
|
||||
})
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('void.toggleSettings', async () => {
|
||||
webview.postMessage({ type: 'toggleSettings' } satisfies MessageToSidebar)
|
||||
}));
|
||||
|
||||
// Receive messages in the extension from the sidebar webview (messages are sent using `postMessage`)
|
||||
webview.onDidReceiveMessage(async (m: MessageFromSidebar) => {
|
||||
|
|
@ -166,9 +115,13 @@ export function activate(context: vscode.ExtensionContext) {
|
|||
displayChangesProvider.refreshDiffAreas(editor.document.uri)
|
||||
|
||||
}
|
||||
else if (m.type === 'getApiConfig') {
|
||||
const apiConfig = getApiConfig()
|
||||
webview.postMessage({ type: 'apiConfig', apiConfig } satisfies MessageToSidebar)
|
||||
else if (m.type === 'getPartialVoidConfig') {
|
||||
const partialVoidConfig = context.globalState.get('partialVoidConfig') ?? {}
|
||||
webview.postMessage({ type: 'partialVoidConfig', partialVoidConfig } satisfies MessageToSidebar)
|
||||
}
|
||||
else if (m.type === 'persistPartialVoidConfig') {
|
||||
const partialVoidConfig = m.partialVoidConfig
|
||||
context.globalState.update('partialVoidConfig', partialVoidConfig)
|
||||
}
|
||||
else if (m.type === 'getAllThreads') {
|
||||
const threads: ChatThreads = context.workspaceState.get('allThreads') ?? {}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
import * as vscode from 'vscode';
|
||||
import { ApiConfig } from './common/sendLLMMessage';
|
||||
import { PartialVoidConfig } from './sidebar/contextForConfig';
|
||||
|
||||
|
||||
|
||||
|
|
@ -42,17 +42,19 @@ type Diff = {
|
|||
type MessageToSidebar = (
|
||||
| { type: 'ctrl+l', selection: CodeSelection } // user presses ctrl+l in the editor
|
||||
| { type: 'files', files: { filepath: vscode.Uri, content: string }[] }
|
||||
| { type: 'apiConfig', apiConfig: ApiConfig }
|
||||
| { type: 'partialVoidConfig', partialVoidConfig: PartialVoidConfig }
|
||||
| { type: 'allThreads', threads: ChatThreads }
|
||||
| { type: 'startNewThread' }
|
||||
| { type: 'toggleThreadSelector' }
|
||||
| { type: 'toggleSettings' }
|
||||
)
|
||||
|
||||
// sidebar -> editor
|
||||
type MessageFromSidebar = (
|
||||
| { type: 'applyChanges', code: string } // user clicks "apply" in the sidebar
|
||||
| { type: 'requestFiles', filepaths: vscode.Uri[] }
|
||||
| { type: 'getApiConfig' }
|
||||
| { type: 'getPartialVoidConfig' }
|
||||
| { type: 'persistPartialVoidConfig', partialVoidConfig: PartialVoidConfig }
|
||||
| { type: 'getAllThreads' }
|
||||
| { type: 'persistThread', thread: ChatThreads[string] }
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,23 +1,38 @@
|
|||
import React, { useState, useEffect, useRef, useCallback, FormEvent } from "react"
|
||||
import { ApiConfig, sendLLMMessage } from "../common/sendLLMMessage"
|
||||
import { CodeSelection, ChatMessage, MessageToSidebar } from "../shared_types"
|
||||
import { awaitVSCodeResponse, getVSCodeAPI, onMessageFromVSCode } from "./getVscodeApi"
|
||||
import { awaitVSCodeResponse, getVSCodeAPI, onMessageFromVSCode, useOnVSCodeMessage } from "./getVscodeApi"
|
||||
|
||||
import { SidebarThreadSelector } from "./SidebarThreadSelector";
|
||||
import { useThreads } from "./threadsContext";
|
||||
import { useThreads } from "./contextForThreads";
|
||||
import { SidebarChat } from "./SidebarChat";
|
||||
import { SidebarSettings } from './SidebarSettings';
|
||||
|
||||
|
||||
|
||||
const Sidebar = () => {
|
||||
const [isThreadSelectorOpen, setIsThreadSelectorOpen] = useState(false)
|
||||
const [requestFailed, setRequestFailed] = useState(false)
|
||||
const [requestFailedReason, setRequestFailedReason] = useState('')
|
||||
const [tab, setTab] = useState<'threadSelector' | 'chat' | 'settings'>('chat')
|
||||
|
||||
// if they pressed the + to add a new chat
|
||||
useOnVSCodeMessage('startNewThread', (m) => {
|
||||
setTab('chat')
|
||||
})
|
||||
|
||||
// if they toggled thread selector
|
||||
useOnVSCodeMessage('toggleThreadSelector', (m) => {
|
||||
if (tab === 'threadSelector')
|
||||
setTab('chat')
|
||||
else
|
||||
setTab('threadSelector')
|
||||
})
|
||||
|
||||
// if they toggled settings
|
||||
useOnVSCodeMessage('toggleSettings', (m) => {
|
||||
if (tab === 'settings')
|
||||
setTab('chat')
|
||||
else
|
||||
setTab('settings')
|
||||
})
|
||||
|
||||
// get Api Config on mount
|
||||
useEffect(() => {
|
||||
getVSCodeAPI().postMessage({ type: 'getApiConfig' })
|
||||
}, [])
|
||||
|
||||
// Receive messages from the VSCode extension
|
||||
useEffect(() => {
|
||||
|
|
@ -30,15 +45,24 @@ const Sidebar = () => {
|
|||
}, [])
|
||||
|
||||
|
||||
return <>
|
||||
<div className="flex flex-col h-screen w-full">
|
||||
{isThreadSelectorOpen && (
|
||||
<div className="mb-2 max-h-[30vh] overflow-y-auto">
|
||||
<SidebarThreadSelector onClose={() => setIsThreadSelectorOpen(false)} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<SidebarChat setIsThreadSelectorOpen={setIsThreadSelectorOpen} />
|
||||
return <>
|
||||
<div className={`flex flex-col h-screen w-full`}>
|
||||
|
||||
<div className={`mb-2 max-h-[30vh] overflow-y-auto ${tab !== 'threadSelector' ? 'hidden' : ''}`}>
|
||||
<SidebarThreadSelector onClose={() => setTab('chat')} />
|
||||
</div>
|
||||
|
||||
<div className={`${tab !== 'chat' && tab !== 'threadSelector' ? 'hidden' : ''}`}>
|
||||
<SidebarChat />
|
||||
</div>
|
||||
|
||||
<div className={`${tab !== 'settings' ? 'hidden' : ''}`}>
|
||||
<SidebarSettings />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -8,8 +8,9 @@ import { SelectedFiles } from "./components/SelectedFiles";
|
|||
import { File, ChatMessage, CodeSelection } from "../shared_types";
|
||||
import * as vscode from 'vscode'
|
||||
import { awaitVSCodeResponse, getVSCodeAPI, onMessageFromVSCode, useOnVSCodeMessage } from "./getVscodeApi";
|
||||
import { useThreads } from "./threadsContext";
|
||||
import { ApiConfig, sendLLMMessage } from "../common/sendLLMMessage";
|
||||
import { useThreads } from "./contextForThreads";
|
||||
import { sendLLMMessage } from "../common/sendLLMMessage";
|
||||
import { useVoidConfig } from "./contextForConfig";
|
||||
|
||||
|
||||
|
||||
|
|
@ -49,7 +50,7 @@ Please edit the file following these instructions:
|
|||
Please edit the selected code following these instructions:
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
str += `
|
||||
\t${instructions}
|
||||
`;
|
||||
|
|
@ -92,7 +93,7 @@ const ChatBubble = ({ chatMessage }: { chatMessage: ChatMessage }) => {
|
|||
}
|
||||
|
||||
|
||||
export const SidebarChat = ({ setIsThreadSelectorOpen }: { setIsThreadSelectorOpen: (v: boolean | ((v: boolean) => boolean)) => void }) => {
|
||||
export const SidebarChat = () => {
|
||||
|
||||
|
||||
// state of current message
|
||||
|
|
@ -105,10 +106,24 @@ export const SidebarChat = ({ setIsThreadSelectorOpen }: { setIsThreadSelectorOp
|
|||
const [isLoading, setIsLoading] = useState(false)
|
||||
const abortFnRef = useRef<(() => void) | null>(null)
|
||||
|
||||
// higher level state
|
||||
const { allThreads, currentThread, addMessageToHistory, startNewThread, } = useThreads()
|
||||
const [apiConfig, setApiConfig] = useState<ApiConfig | null>(null)
|
||||
const [latestError, setLatestError] = useState('')
|
||||
|
||||
// higher level state
|
||||
const { allThreads, currentThread, addMessageToHistory, startNewThread, switchToThread } = useThreads()
|
||||
const { voidConfig } = useVoidConfig()
|
||||
|
||||
// if they pressed the + to add a new chat
|
||||
useOnVSCodeMessage('startNewThread', (m) => {
|
||||
// find a thread with 0 messages and switch to it
|
||||
for (let threadId in allThreads) {
|
||||
if (allThreads[threadId].messages.length === 0) {
|
||||
switchToThread(threadId)
|
||||
return
|
||||
}
|
||||
}
|
||||
// start a new thread
|
||||
startNewThread()
|
||||
})
|
||||
|
||||
// if user pressed ctrl+l, add their selection to the sidebar
|
||||
useOnVSCodeMessage('ctrl+l', (m) => {
|
||||
|
|
@ -120,24 +135,6 @@ export const SidebarChat = ({ setIsThreadSelectorOpen }: { setIsThreadSelectorOp
|
|||
setFiles(files => [...files, filepath])
|
||||
})
|
||||
|
||||
// when get apiConfig, set
|
||||
useOnVSCodeMessage('apiConfig', (m) => {
|
||||
setApiConfig(m.apiConfig)
|
||||
})
|
||||
|
||||
// if they pressed the + to add a new chat
|
||||
useOnVSCodeMessage('startNewThread', (m) => {
|
||||
setIsThreadSelectorOpen(false)
|
||||
if (currentThread?.messages.length !== 0)
|
||||
startNewThread()
|
||||
|
||||
})
|
||||
|
||||
// if they opened thread selector
|
||||
useOnVSCodeMessage('toggleThreadSelector', (m) => {
|
||||
setIsThreadSelectorOpen(v => !v)
|
||||
})
|
||||
|
||||
|
||||
const formRef = useRef<HTMLFormElement | null>(null)
|
||||
const onSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
||||
|
|
@ -147,34 +144,43 @@ export const SidebarChat = ({ setIsThreadSelectorOpen }: { setIsThreadSelectorOp
|
|||
|
||||
setIsLoading(true)
|
||||
setInstructions('');
|
||||
formRef.current?.reset(); // reset the form's text
|
||||
formRef.current?.reset(); // reset the form's text when clear instructions or unexpected behavior happens
|
||||
setSelection(null)
|
||||
setFiles([])
|
||||
setLatestError('')
|
||||
|
||||
// request file content from vscode and await response
|
||||
getVSCodeAPI().postMessage({ type: 'requestFiles', filepaths: files })
|
||||
const relevantFiles = await awaitVSCodeResponse('files')
|
||||
|
||||
// add message to chat history
|
||||
const content = userInstructionsStr(instructions, relevantFiles.files, selection)
|
||||
const userContent = userInstructionsStr(instructions, relevantFiles.files, selection)
|
||||
// console.log('prompt:\n', content)
|
||||
const newHistoryElt: ChatMessage = { role: 'user', content, displayContent: instructions, selection, files }
|
||||
const newHistoryElt: ChatMessage = { role: 'user', content: userContent, displayContent: instructions, selection, files }
|
||||
addMessageToHistory(newHistoryElt)
|
||||
|
||||
// send message to LLM
|
||||
let { abort } = sendLLMMessage({
|
||||
messages: [...(currentThread?.messages ?? []).map(m => ({ role: m.role, content: m.content })), { role: 'user', content }],
|
||||
messages: [...(currentThread?.messages ?? []).map(m => ({ role: m.role, content: m.content })), { role: 'user', content: userContent }],
|
||||
onText: (newText, fullText) => setMessageStream(fullText),
|
||||
onFinalMessage: (content) => {
|
||||
// add assistant's message to chat history, and clear selection
|
||||
const newHistoryElt: ChatMessage = { role: 'assistant', content, displayContent: content, }
|
||||
addMessageToHistory(newHistoryElt)
|
||||
|
||||
// clear selection
|
||||
setMessageStream('')
|
||||
setIsLoading(false)
|
||||
},
|
||||
apiConfig: apiConfig
|
||||
onError: (error) => {
|
||||
// add assistant's message to chat history, and clear selection
|
||||
let content = messageStream; // just use the current content
|
||||
const newHistoryElt: ChatMessage = { role: 'assistant', content, displayContent: content, }
|
||||
addMessageToHistory(newHistoryElt)
|
||||
setMessageStream('')
|
||||
setIsLoading(false)
|
||||
|
||||
setLatestError(error)
|
||||
},
|
||||
voidConfig: voidConfig
|
||||
})
|
||||
abortFnRef.current = abort
|
||||
|
||||
|
|
@ -273,6 +279,8 @@ export const SidebarChat = ({ setIsThreadSelectorOpen }: { setIsThreadSelectorOp
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{latestError}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
|
|
|||
96
extensions/void/src/sidebar/SidebarSettings.tsx
Normal file
96
extensions/void/src/sidebar/SidebarSettings.tsx
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
import React, { useState } from "react";
|
||||
import { configFields, useVoidConfig, VoidConfigField } from "./contextForConfig";
|
||||
|
||||
|
||||
const SettingOfFieldAndParam = ({ field, param }: { field: VoidConfigField, param: string }) => {
|
||||
const { voidConfig, partialVoidConfig, voidConfigInfo, setConfigParam } = useVoidConfig()
|
||||
const { enumArr, defaultVal, description } = voidConfigInfo[field][param]
|
||||
const val = partialVoidConfig[field]?.[param] ?? defaultVal // current value of this item
|
||||
|
||||
const updateState = (newValue: string) => { setConfigParam(field, param, newValue) }
|
||||
|
||||
const resetButton = <button className='btn btn-sm' onClick={() => updateState(defaultVal)}>
|
||||
<svg
|
||||
className='size-5'
|
||||
stroke="currentColor" fill="currentColor" strokeWidth="0" viewBox="0 0 16 16" height="200px" width="200px" xmlns="http://www.w3.org/2000/svg"><path fillRule="evenodd" clipRule="evenodd" d="M3.5 2v3.5L4 6h3.5V5H4.979l.941-.941a3.552 3.552 0 1 1 5.023 5.023L5.746 14.28l.72.72 5.198-5.198A4.57 4.57 0 0 0 5.2 3.339l-.7.7V2h-1z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
const inputElement = enumArr === undefined ?
|
||||
// string
|
||||
(<input
|
||||
className='input p-1 w-full'
|
||||
type="text"
|
||||
value={val}
|
||||
onChange={(e) => updateState(e.target.value)}
|
||||
/>)
|
||||
:
|
||||
// enum
|
||||
(<select
|
||||
className='dropdown p-1 w-full'
|
||||
value={val}
|
||||
onChange={(e) => updateState(e.target.value)}
|
||||
>
|
||||
{enumArr.map((option) => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>)
|
||||
|
||||
return <div>
|
||||
<label className='hidden'>{param}</label>
|
||||
<span>{description}</span>
|
||||
<div className='flex items-center'>
|
||||
{inputElement}
|
||||
{resetButton}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
export const SidebarSettings = () => {
|
||||
|
||||
const { voidConfig, voidConfigInfo } = useVoidConfig()
|
||||
|
||||
const current_field = voidConfig.default['whichApi'] as VoidConfigField
|
||||
|
||||
|
||||
return (
|
||||
<div className='space-y-4 py-2 overflow-y-auto'>
|
||||
|
||||
{/* choose the field */}
|
||||
<div className='outline-vscode-input-bg'>
|
||||
<SettingOfFieldAndParam
|
||||
field='default'
|
||||
param='whichApi'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
{/* render all fields, but hide the ones not visible for fast tab switching */}
|
||||
{configFields.map(field => {
|
||||
return <div
|
||||
key={field}
|
||||
className={`flex flex-col gap-y-2 ${field !== current_field ? 'hidden' : ''}`}
|
||||
>
|
||||
{Object.keys(voidConfigInfo[field]).map((param) => (
|
||||
<SettingOfFieldAndParam
|
||||
key={param}
|
||||
field={field}
|
||||
param={param}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
})}
|
||||
|
||||
{/* Remove this after 10/21/24, this is just to give developers a heads up about the recent change */}
|
||||
<div className='pt-20'>
|
||||
{`We recently updated Settings. To copy your old Void settings over, press Ctrl+Shift+P, `}
|
||||
{`type 'Open User Settings (JSON)',`}
|
||||
{` and look for 'void.'. `}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -1,10 +1,26 @@
|
|||
import React from "react";
|
||||
import { ThreadsProvider, useThreads } from "./threadsContext";
|
||||
import { ThreadsProvider, useThreads } from "./contextForThreads";
|
||||
|
||||
|
||||
const truncate = (s: string) => {
|
||||
let len = s.length
|
||||
const TRUNC_AFTER = 16
|
||||
if (len >= TRUNC_AFTER)
|
||||
s = s.substring(0, TRUNC_AFTER) + '...'
|
||||
return s
|
||||
}
|
||||
|
||||
|
||||
export const SidebarThreadSelector = ({ onClose }: { onClose: () => void }) => {
|
||||
const { allThreads, currentThread, switchToThread } = useThreads()
|
||||
|
||||
// sorted by most recent to least recent
|
||||
const sortedThreadIds = Object.keys(allThreads ?? {}).sort((threadId1, threadId2) => allThreads![threadId1].createdAt > allThreads![threadId2].createdAt ? -1 : 1)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col space-y-1">
|
||||
<div className="flex flex-col gap-y-1">
|
||||
|
||||
{/* X button at top right */}
|
||||
<div className="text-right">
|
||||
<button className="btn btn-sm" onClick={onClose}>
|
||||
<svg
|
||||
|
|
@ -22,19 +38,40 @@ export const SidebarThreadSelector = ({ onClose }: { onClose: () => void }) => {
|
|||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{/* iterate through all past threads */}
|
||||
{Object.keys(allThreads ?? {}).map((threadId) => {
|
||||
const pastThread = (allThreads ?? {})[threadId];
|
||||
return (
|
||||
<button
|
||||
key={pastThread.id}
|
||||
className={`btn btn-sm btn-secondary ${pastThread.id === currentThread?.id ? "btn-primary" : ""}`}
|
||||
onClick={() => switchToThread(pastThread.id)}
|
||||
>
|
||||
{new Date(pastThread.createdAt).toLocaleString()}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
|
||||
{/* a list of all the past threads */}
|
||||
<div className='flex flex-col gap-y-1 max-h-80 overflow-y-auto'>
|
||||
{sortedThreadIds.map((threadId) => {
|
||||
if (!allThreads)
|
||||
return <>Error: Threads not found.</>
|
||||
const pastThread = allThreads[threadId]
|
||||
|
||||
let btnStringArr = []
|
||||
|
||||
let msg1 = truncate(allThreads[threadId].messages[0]?.displayContent ?? '(empty)')
|
||||
btnStringArr.push(msg1)
|
||||
|
||||
let msg2 = truncate(allThreads[threadId].messages[1]?.displayContent ?? '')
|
||||
if (msg2)
|
||||
btnStringArr.push(msg2)
|
||||
|
||||
btnStringArr.push(allThreads[threadId].messages.length)
|
||||
|
||||
const btnString = btnStringArr.join(' / ')
|
||||
|
||||
return (
|
||||
<button
|
||||
key={pastThread.id}
|
||||
className={`btn btn-sm rounded-sm ${pastThread.id === currentThread?.id ? "btn-primary" : "btn-secondary"}`}
|
||||
onClick={() => switchToThread(pastThread.id)}
|
||||
title={new Date(pastThread.createdAt).toLocaleString()}
|
||||
>
|
||||
{btnString}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
360
extensions/void/src/sidebar/contextForConfig.tsx
Normal file
360
extensions/void/src/sidebar/contextForConfig.tsx
Normal file
|
|
@ -0,0 +1,360 @@
|
|||
import React, { ReactNode, createContext, useCallback, useContext, useEffect, useRef, useState, } from "react"
|
||||
import { awaitVSCodeResponse, getVSCodeAPI, useOnVSCodeMessage } from "./getVscodeApi"
|
||||
|
||||
const configEnum = <EnumArr extends readonly string[]>(description: string, defaultVal: EnumArr[number], enumArr: EnumArr) => {
|
||||
return {
|
||||
description,
|
||||
defaultVal,
|
||||
enumArr,
|
||||
}
|
||||
}
|
||||
|
||||
const configString = (description: string, defaultVal: string) => {
|
||||
return {
|
||||
description,
|
||||
defaultVal,
|
||||
enumArr: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
// fields you can customize (don't forget 'default' - it isn't included here!)
|
||||
export const configFields = [
|
||||
'anthropic',
|
||||
'openAI',
|
||||
'greptile',
|
||||
'ollama',
|
||||
'openRouter',
|
||||
'openAICompatible',
|
||||
'azure'
|
||||
] as const
|
||||
|
||||
|
||||
|
||||
const voidConfigInfo: Record<
|
||||
typeof configFields[number] | 'default', {
|
||||
[prop: string]: {
|
||||
description: string,
|
||||
enumArr?: readonly string[] | undefined,
|
||||
defaultVal: string,
|
||||
},
|
||||
}
|
||||
> = {
|
||||
default: {
|
||||
whichApi: configEnum(
|
||||
"API Provider.",
|
||||
'anthropic',
|
||||
configFields,
|
||||
),
|
||||
},
|
||||
anthropic: {
|
||||
apikey: configString('Anthropic API key.', ''),
|
||||
model: configEnum(
|
||||
"Anthropic model to use.",
|
||||
'claude-3-5-sonnet-20240620',
|
||||
[
|
||||
"claude-3-5-sonnet-20240620",
|
||||
"claude-3-opus-20240229",
|
||||
"claude-3-sonnet-20240229",
|
||||
"claude-3-haiku-20240307"
|
||||
] as const,
|
||||
),
|
||||
|
||||
maxTokens: configEnum(
|
||||
"Anthropic max number of tokens to output.",
|
||||
'8192',
|
||||
[
|
||||
"1024",
|
||||
"2048",
|
||||
"4096",
|
||||
"8192"
|
||||
] as const,
|
||||
),
|
||||
},
|
||||
openAI: {
|
||||
apikey: configString('OpenAI API key.', ''),
|
||||
model: configEnum(
|
||||
'OpenAI model to use.',
|
||||
'gpt-4o',
|
||||
[
|
||||
"o1-preview",
|
||||
"o1-mini",
|
||||
"gpt-4o",
|
||||
"gpt-4o-2024-05-13",
|
||||
"gpt-4o-2024-08-06",
|
||||
"gpt-4o-mini",
|
||||
"gpt-4o-mini-2024-07-18",
|
||||
"gpt-4-turbo",
|
||||
"gpt-4-turbo-2024-04-09",
|
||||
"gpt-4-turbo-preview",
|
||||
"gpt-4-0125-preview",
|
||||
"gpt-4-1106-preview",
|
||||
"gpt-4",
|
||||
"gpt-4-0613",
|
||||
"gpt-3.5-turbo-0125",
|
||||
"gpt-3.5-turbo",
|
||||
"gpt-3.5-turbo-1106"
|
||||
] as const
|
||||
),
|
||||
},
|
||||
greptile: {
|
||||
apikey: configString('Greptile API key.', ''),
|
||||
githubPAT: configString('Github PAT that Greptile uses to access your repository', ''),
|
||||
remote: configEnum(
|
||||
'Repo location',
|
||||
'github',
|
||||
[
|
||||
'github',
|
||||
'gitlab'
|
||||
] as const
|
||||
),
|
||||
repository: configString('Repository identifier in "owner/repository" format.', ''),
|
||||
branch: configString('Name of the branch to use.', 'main'),
|
||||
},
|
||||
ollama: {
|
||||
endpoint: configString(
|
||||
'The Ollama endpoint. Start Ollama by running `OLLAMA_ORIGINS="vscode-webview://*" ollama serve`',
|
||||
'http://127.0.0.1:11434'
|
||||
),
|
||||
model: configEnum(
|
||||
'Ollama model to use.',
|
||||
'llama3.1',
|
||||
[
|
||||
"codegemma",
|
||||
"codegemma:2b",
|
||||
"codegemma:7b",
|
||||
"codellama",
|
||||
"codellama:7b",
|
||||
"codellama:13b",
|
||||
"codellama:34b",
|
||||
"codellama:70b",
|
||||
"codellama:code",
|
||||
"codellama:python",
|
||||
"command-r",
|
||||
"command-r:35b",
|
||||
"command-r-plus",
|
||||
"command-r-plus:104b",
|
||||
"deepseek-coder-v2",
|
||||
"deepseek-coder-v2:16b",
|
||||
"deepseek-coder-v2:236b",
|
||||
"falcon2",
|
||||
"falcon2:11b",
|
||||
"firefunction-v2",
|
||||
"firefunction-v2:70b",
|
||||
"gemma",
|
||||
"gemma:2b",
|
||||
"gemma:7b",
|
||||
"gemma2",
|
||||
"gemma2:2b",
|
||||
"gemma2:9b",
|
||||
"gemma2:27b",
|
||||
"llama2",
|
||||
"llama2:7b",
|
||||
"llama2:13b",
|
||||
"llama2:70b",
|
||||
"llama3",
|
||||
"llama3:8b",
|
||||
"llama3:70b",
|
||||
"llama3-chatqa",
|
||||
"llama3-chatqa:8b",
|
||||
"llama3-chatqa:70b",
|
||||
"llama3-gradient",
|
||||
"llama3-gradient:8b",
|
||||
"llama3-gradient:70b",
|
||||
"llama3.1",
|
||||
"llama3.1:8b",
|
||||
"llama3.1:70b",
|
||||
"llama3.1:405b",
|
||||
"llava",
|
||||
"llava:7b",
|
||||
"llava:13b",
|
||||
"llava:34b",
|
||||
"llava-llama3",
|
||||
"llava-llama3:8b",
|
||||
"llava-phi3",
|
||||
"llava-phi3:3.8b",
|
||||
"mistral",
|
||||
"mistral:7b",
|
||||
"mistral-large",
|
||||
"mistral-large:123b",
|
||||
"mistral-nemo",
|
||||
"mistral-nemo:12b",
|
||||
"mixtral",
|
||||
"mixtral:8x7b",
|
||||
"mixtral:8x22b",
|
||||
"moondream",
|
||||
"moondream:1.8b",
|
||||
"openhermes",
|
||||
"openhermes:v2.5",
|
||||
"phi3",
|
||||
"phi3:3.8b",
|
||||
"phi3:14b",
|
||||
"phi3.5",
|
||||
"phi3.5:3.8b",
|
||||
"qwen",
|
||||
"qwen:7b",
|
||||
"qwen:14b",
|
||||
"qwen:32b",
|
||||
"qwen:72b",
|
||||
"qwen:110b",
|
||||
"qwen2",
|
||||
"qwen2:0.5b",
|
||||
"qwen2:1.5b",
|
||||
"qwen2:7b",
|
||||
"qwen2:72b",
|
||||
"smollm",
|
||||
"smollm:135m",
|
||||
"smollm:360m",
|
||||
"smollm:1.7b"
|
||||
] as const
|
||||
),
|
||||
},
|
||||
openRouter: {
|
||||
model: configString(
|
||||
'OpenRouter model to use.',
|
||||
'openai/gpt-4o'
|
||||
),
|
||||
apikey: configString('OpenRouter API key.', ''),
|
||||
},
|
||||
openAICompatible: {
|
||||
endpoint: configString('The endpoint.', 'http://127.0.0.1:11434/v1'),
|
||||
model: configString('The name of the model to use.', 'gpt-4o'),
|
||||
apikey: configString('Your API key.', ''),
|
||||
},
|
||||
azure: {
|
||||
// "void.azure.apiKey": {
|
||||
// "type": "string",
|
||||
// "description": "Azure API key."
|
||||
// },
|
||||
// "void.azure.deploymentId": {
|
||||
// "type": "string",
|
||||
// "description": "Azure API deployment ID."
|
||||
// },
|
||||
// "void.azure.resourceName": {
|
||||
// "type": "string",
|
||||
// "description": "Name of the Azure OpenAI resource. Either this or `baseURL` can be used. \nThe resource name is used in the assembled URL: `https://{resourceName}.openai.azure.com/openai/deployments/{modelId}{path}`"
|
||||
// },
|
||||
// "void.azure.providerSettings": {
|
||||
// "type": "object",
|
||||
// "properties": {
|
||||
// "baseURL": {
|
||||
// "type": "string",
|
||||
// "default": "https://${resourceName}.openai.azure.com/openai/deployments",
|
||||
// "description": "Azure API base URL."
|
||||
// },
|
||||
// "headers": {
|
||||
// "type": "object",
|
||||
// "description": "Custom headers to include in the requests."
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
// this is the type that comes with metadata like desc, default val, etc
|
||||
type VoidConfigInfo = typeof voidConfigInfo
|
||||
export type VoidConfigField = keyof typeof voidConfigInfo // typeof configFields[number]
|
||||
|
||||
// this is the type that specifies the user's actual config
|
||||
export type PartialVoidConfig = {
|
||||
[K in keyof typeof voidConfigInfo]?: {
|
||||
[P in keyof typeof voidConfigInfo[K]]?: typeof voidConfigInfo[K][P]['defaultVal']
|
||||
}
|
||||
}
|
||||
|
||||
export type VoidConfig = {
|
||||
[K in keyof typeof voidConfigInfo]: {
|
||||
[P in keyof typeof voidConfigInfo[K]]: typeof voidConfigInfo[K][P]['defaultVal']
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
const getVoidConfig = (currentConfig: PartialVoidConfig): VoidConfig => {
|
||||
const config = {} as PartialVoidConfig
|
||||
for (let field of [...configFields, 'default'] as const) {
|
||||
config[field] = {}
|
||||
for (let prop in voidConfigInfo[field]) {
|
||||
config[field][prop] = currentConfig[field]?.[prop] || voidConfigInfo[field][prop].defaultVal
|
||||
}
|
||||
}
|
||||
return config as VoidConfig
|
||||
}
|
||||
|
||||
const defaultVoidConfig: VoidConfig = getVoidConfig({})
|
||||
|
||||
// const [stateRef, setState] = useInstantState(initVal)
|
||||
// setState instantly changes the value of stateRef instead of having to wait until the next render
|
||||
const useInstantState = <T,>(initVal: T) => {
|
||||
const stateRef = useRef<T>(initVal)
|
||||
const [_, setS] = useState<T>(initVal)
|
||||
const setState = useCallback((newVal: T) => {
|
||||
setS(newVal);
|
||||
stateRef.current = newVal;
|
||||
}, [])
|
||||
return [stateRef as React.RefObject<T>, setState] as const // make s.current readonly - setState handles all changes
|
||||
}
|
||||
|
||||
|
||||
|
||||
type SetConfigParamType = <K extends VoidConfigField>(field: K, param: keyof VoidConfigInfo[K], newVal: string) => void
|
||||
|
||||
type ConfigValueType = {
|
||||
voidConfig: VoidConfig,
|
||||
voidConfigInfo: VoidConfigInfo,
|
||||
partialVoidConfig: PartialVoidConfig,
|
||||
setConfigParam: SetConfigParamType
|
||||
}
|
||||
|
||||
|
||||
const ConfigContext = createContext<ConfigValueType>(undefined as unknown as ConfigValueType)
|
||||
|
||||
export function ConfigProvider({ children }: { children: ReactNode }) {
|
||||
const [partialVoidConfig, setPartialVoidConfig] = useInstantState<PartialVoidConfig>({}) // the user's selections
|
||||
const [voidConfig, setVoidConfig] = useState<VoidConfig>(defaultVoidConfig)
|
||||
|
||||
|
||||
// get the config on mount
|
||||
useEffect(() => {
|
||||
getVSCodeAPI().postMessage({ type: 'getPartialVoidConfig' })
|
||||
awaitVSCodeResponse('partialVoidConfig').then((m) => {
|
||||
setPartialVoidConfig(m.partialVoidConfig)
|
||||
const newFullConfig = getVoidConfig(m.partialVoidConfig)
|
||||
setVoidConfig(newFullConfig)
|
||||
})
|
||||
}, [setPartialVoidConfig])
|
||||
|
||||
// return the provider
|
||||
return (<ConfigContext.Provider
|
||||
value={{
|
||||
voidConfig,
|
||||
voidConfigInfo,
|
||||
partialVoidConfig: partialVoidConfig.current ?? {},
|
||||
setConfigParam: (field, param, newVal) => {
|
||||
const newPartialConfig: PartialVoidConfig = {
|
||||
...partialVoidConfig.current,
|
||||
[field]: {
|
||||
...partialVoidConfig.current?.[field],
|
||||
[param]: newVal
|
||||
}
|
||||
}
|
||||
setPartialVoidConfig(newPartialConfig)
|
||||
const newFullConfig = getVoidConfig(newPartialConfig)
|
||||
setVoidConfig(newFullConfig)
|
||||
getVSCodeAPI().postMessage({ type: 'persistPartialVoidConfig', partialVoidConfig: newPartialConfig })
|
||||
}
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ConfigContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useVoidConfig(): ConfigValueType {
|
||||
const context = useContext<ConfigValueType>(ConfigContext)
|
||||
if (context === undefined) {
|
||||
throw new Error("useVoidConfig missing Provider")
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
|
|
@ -3,7 +3,8 @@ import { ChatMessage, ChatThreads } from "../shared_types"
|
|||
import { awaitVSCodeResponse, getVSCodeAPI } from "./getVscodeApi"
|
||||
|
||||
|
||||
type ThreadsContextValue = {
|
||||
// a "thread" means a chat message history
|
||||
type ConfigForThreadsValueType = {
|
||||
readonly allThreads: ChatThreads | null,
|
||||
readonly currentThread: ChatThreads[string] | null;
|
||||
addMessageToHistory: (message: ChatMessage) => void;
|
||||
|
|
@ -11,7 +12,7 @@ type ThreadsContextValue = {
|
|||
startNewThread: () => void;
|
||||
}
|
||||
|
||||
const ThreadsContext = createContext<ThreadsContextValue>(undefined as unknown as ThreadsContextValue)
|
||||
const ThreadsContext = createContext<ConfigForThreadsValueType>(undefined as unknown as ConfigForThreadsValueType)
|
||||
|
||||
const createNewThread = () => ({
|
||||
id: new Date().getTime().toString(),
|
||||
|
|
@ -39,7 +40,7 @@ export function ThreadsProvider({ children }: { children: ReactNode }) {
|
|||
|
||||
// this loads allThreads in on mount
|
||||
useEffect(() => {
|
||||
getVSCodeAPI().postMessage({ type: "getAllThreads" })
|
||||
getVSCodeAPI().postMessage({ type: 'getAllThreads' })
|
||||
awaitVSCodeResponse('allThreads')
|
||||
.then(response => {
|
||||
setAllThreads(response.threads)
|
||||
|
|
@ -90,10 +91,10 @@ export function ThreadsProvider({ children }: { children: ReactNode }) {
|
|||
)
|
||||
}
|
||||
|
||||
export function useThreads(): ThreadsContextValue {
|
||||
const context = useContext<ThreadsContextValue>(ThreadsContext)
|
||||
export function useThreads(): ConfigForThreadsValueType {
|
||||
const context = useContext<ConfigForThreadsValueType>(ThreadsContext)
|
||||
if (context === undefined) {
|
||||
throw new Error("useThreads must be used within a ThreadsProvider")
|
||||
throw new Error("useThreads missing Provider")
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
|
@ -9,20 +9,22 @@ type Command = MessageToSidebar['type']
|
|||
const onetimeCallbacks: { [C in Command]: ((res: any) => void)[] } = {
|
||||
"ctrl+l": [],
|
||||
"files": [],
|
||||
"apiConfig": [],
|
||||
"partialVoidConfig": [],
|
||||
"startNewThread": [],
|
||||
"allThreads": [],
|
||||
"toggleThreadSelector": []
|
||||
"toggleThreadSelector": [],
|
||||
"toggleSettings": [],
|
||||
}
|
||||
|
||||
// messageType -> id -> res
|
||||
const callbacks: { [C in Command]: { [id: string]: ((res: any) => void) } } = {
|
||||
"ctrl+l": {},
|
||||
"files": {},
|
||||
"apiConfig": {},
|
||||
"partialVoidConfig": {},
|
||||
"startNewThread": {},
|
||||
"allThreads": {},
|
||||
"toggleThreadSelector": {}
|
||||
"toggleThreadSelector": {},
|
||||
"toggleSettings": {},
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import * as React from "react"
|
||||
import * as ReactDOM from "react-dom/client"
|
||||
import Sidebar from "./Sidebar"
|
||||
import { ThreadsProvider } from "./threadsContext"
|
||||
import { ThreadsProvider } from "./contextForThreads"
|
||||
import { ConfigProvider } from "./contextForConfig"
|
||||
|
||||
// mount the sidebar on the id="root" element
|
||||
if (typeof document === "undefined") {
|
||||
|
|
@ -13,7 +14,9 @@ console.log("Void root Element:", rootElement)
|
|||
|
||||
const extension = (
|
||||
<ThreadsProvider>
|
||||
<Sidebar />
|
||||
<ConfigProvider>
|
||||
<Sidebar />
|
||||
</ConfigProvider>
|
||||
</ThreadsProvider>
|
||||
)
|
||||
const root = ReactDOM.createRoot(rootElement)
|
||||
|
|
|
|||
|
|
@ -35,5 +35,9 @@ html {
|
|||
}
|
||||
|
||||
.input {
|
||||
@apply bg-vscode-input-bg text-vscode-input-fg border-vscode-input-border;
|
||||
@apply bg-vscode-input-bg text-vscode-input-fg border-vscode-input-border focus:outline-vscode-focus-border;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
@apply bg-vscode-dropdown-bg text-vscode-dropdown-foreground border-vscode-dropdown-border focus:outline-vscode-focus-border;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,8 +18,11 @@ module.exports = {
|
|||
"button-hoverBg": "var(--vscode-button-hoverBackground)",
|
||||
"button-secondary-fg": "var(--vscode-button-secondaryForeground)",
|
||||
"button-secondary-bg": "var(--vscode-button-secondaryBackground)",
|
||||
"button-secondary-hoverBg":
|
||||
"var(--vscode-button-secondaryHoverBackground)",
|
||||
"button-secondary-hoverBg": "var(--vscode-button-secondaryHoverBackground)",
|
||||
"dropdown-bg": "var(--vscode-settings-dropdownBackground)",
|
||||
"dropdown-foreground": "var(--vscode-settings-dropdownForeground)",
|
||||
"dropdown-border": "var(--vscode-settings-dropdownBorder)",
|
||||
"focus-border": "var(--vscode-focusBorder)",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue