Compare commits

..

20 Commits

Author SHA1 Message Date
Andrew Pareles 8a6daa5766 very small changes 2024-11-26 16:45:08 -08:00
Andrew Pareles 9ec4f3da01 void useful links update 2024-11-26 16:39:01 -08:00
Andrew Pareles 86c9a17f8d improve message 2024-11-26 02:59:16 -08:00
Andrew Pareles d21f2c4d62 fix file capitalization part 2 2024-11-26 02:52:20 -08:00
Andrew Pareles 733256feab fix file capitalization part 1 2024-11-26 02:51:56 -08:00
Andrew Pareles f9ce9de316 Merge pull request #159 from voideditor/fix-cors
Fix CORS + CSP
2024-11-26 02:22:57 -08:00
Andrew Pareles 9f27b17f11 ignore e.stack 2024-11-26 02:22:05 -08:00
Andrew Pareles 8ff4f1c6e1 update error handling 2024-11-26 02:16:57 -08:00
Andrew Pareles f6f6259f23 fix error handling 2024-11-26 01:55:42 -08:00
Andrew Pareles c7e9175fd7 add ability to abort 2024-11-26 01:08:25 -08:00
Andrew Pareles bdc3c9cb7e proxy works! 2024-11-26 00:41:31 -08:00
Andrew Pareles 790a2b6a4f llm messages work! sent from node! 2024-11-26 00:25:47 -08:00
Andrew Pareles e6c779d91a progress 2024-11-24 23:43:10 -08:00
Andrew Pareles aa95ceafd2 proxy to node appears to work!! 2024-11-24 20:39:15 -08:00
Andrew Pareles 9ef1aa2c60 fix cors progress 2024-11-24 20:28:54 -08:00
Andrew Pareles e4107e1257 add server? 2024-11-24 17:53:57 -08:00
Andrew Pareles 1d5fef43c1 improved error handling 2024-11-23 16:05:26 -08:00
Andrew Pareles 51280d4e77 error ui improvements 2024-11-22 00:43:07 -08:00
Andrew Pareles d5ccc6f777 minor changes 2024-11-21 22:37:31 -08:00
Andrew Pareles c58af2a3af Merge pull request #139 from voideditor/actual-editor-insets
Native Editor Insets
2024-11-21 18:32:25 -08:00
30 changed files with 693 additions and 358 deletions
+4 -3
View File
@@ -4,6 +4,8 @@ The Void team put together this list of links to get up and running with VSCode'
## Beginners / Getting started
<!-- - [Void's Guide](https://voideditor.com) to VSCode - we put together covers VSCode's source. -->
- [VSCode UI guide](https://code.visualstudio.com/docs/getstarted/userinterface) - covers auxbar, panels, etc.
- [UX guide](https://code.visualstudio.com/api/ux-guidelines/overview) - covers Containers, Views, Items, etc.
@@ -12,13 +14,12 @@ The Void team put together this list of links to get up and running with VSCode'
- [How VS Code's sourcecode is organized](https://github.com/microsoft/vscode/wiki/Source-Code-Organization) - this explains where the entry point files are, what `browser/` and `common/` mean, etc. This is the most important read on this whole list! We recommend reading the whole thing.
- [Every command](https://code.visualstudio.com/api/references/commands) built-in to VSCode - sometimes useful to reference.
- [Every command](https://code.visualstudio.com/api/references/commands) built-in to VSCode - sometimes useful to reference (especially if you're building an extension).
## VSCode's Extension API
Void is mainly an extension right now, and these links were very useful for us to get set up.
Void is no longer an extension, so these links are no longer required, but they might be useful if we ever build an extension again.
- [Files you need in an extension](https://code.visualstudio.com/api/get-started/extension-anatomy).
+85 -2
View File
@@ -23,6 +23,7 @@
"@vscode/sudo-prompt": "9.3.1",
"@vscode/tree-sitter-wasm": "^0.0.4",
"@vscode/vscode-languagedetection": "1.0.21",
"@vscode/webview-ui-toolkit": "^1.4.0",
"@vscode/windows-mutex": "^0.5.0",
"@vscode/windows-process-tree": "^0.6.0",
"@vscode/windows-registry": "^1.1.0",
@@ -38,6 +39,7 @@
"https-proxy-agent": "^7.0.2",
"jschardet": "3.1.3",
"kerberos": "2.1.1",
"lucide-react": "^0.460.0",
"minimist": "^1.2.6",
"native-is-elevated": "0.7.0",
"native-keymap": "^3.3.5",
@@ -2065,6 +2067,52 @@
"resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.9.tgz",
"integrity": "sha512-n1VPsljTSkthsAFYdiWfC+DKzK2WwcRp83Y1YAqdX552BstvsDjft9YXppjUzp11BPsapDoO1LDgrDB0XVsfNQ=="
},
"node_modules/@microsoft/fast-element": {
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/@microsoft/fast-element/-/fast-element-1.14.0.tgz",
"integrity": "sha512-zXvuSOzvsu8zDTy9eby8ix8VqLop2rwKRgp++ZN2kTCsoB3+QJVoaGD2T/Cyso2ViZQFXNpiNCVKfnmxBvmWkQ==",
"license": "MIT"
},
"node_modules/@microsoft/fast-foundation": {
"version": "2.50.0",
"resolved": "https://registry.npmjs.org/@microsoft/fast-foundation/-/fast-foundation-2.50.0.tgz",
"integrity": "sha512-8mFYG88Xea1jZf2TI9Lm/jzZ6RWR8x29r24mGuLojNYqIR2Bl8+hnswoV6laApKdCbGMPKnsAL/O68Q0sRxeVg==",
"license": "MIT",
"dependencies": {
"@microsoft/fast-element": "^1.14.0",
"@microsoft/fast-web-utilities": "^5.4.1",
"tabbable": "^5.2.0",
"tslib": "^1.13.0"
}
},
"node_modules/@microsoft/fast-foundation/node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
"license": "0BSD"
},
"node_modules/@microsoft/fast-react-wrapper": {
"version": "0.3.25",
"resolved": "https://registry.npmjs.org/@microsoft/fast-react-wrapper/-/fast-react-wrapper-0.3.25.tgz",
"integrity": "sha512-jKzmk2xJV93RL/jEFXEZgBvXlKIY4N4kXy3qrjmBfFpqNi3VjY+oUTWyMnHRMC5EUhIFxD+Y1VD4u9uIPX3jQw==",
"license": "MIT",
"dependencies": {
"@microsoft/fast-element": "^1.14.0",
"@microsoft/fast-foundation": "^2.50.0"
},
"peerDependencies": {
"react": ">=16.9.0"
}
},
"node_modules/@microsoft/fast-web-utilities": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/@microsoft/fast-web-utilities/-/fast-web-utilities-5.4.1.tgz",
"integrity": "sha512-ReWYncndjV3c8D8iq9tp7NcFNc1vbVHvcBFPME2nNFKNbS1XCesYZGlIlf3ot5EmuOXPlrzUHOWzQ2vFpIkqDg==",
"license": "MIT",
"dependencies": {
"exenv-es6": "^1.1.1"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -4111,6 +4159,21 @@
"node": ">= 0.6"
}
},
"node_modules/@vscode/webview-ui-toolkit": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@vscode/webview-ui-toolkit/-/webview-ui-toolkit-1.4.0.tgz",
"integrity": "sha512-modXVHQkZLsxgmd5yoP3ptRC/G8NBDD+ob+ngPiWNQdlrH6H1xR/qgOBD85bfU3BhOB5sZzFWBwwhp9/SfoHww==",
"license": "MIT",
"dependencies": {
"@microsoft/fast-element": "^1.12.0",
"@microsoft/fast-foundation": "^2.49.4",
"@microsoft/fast-react-wrapper": "^0.3.22",
"tslib": "^2.6.2"
},
"peerDependencies": {
"react": ">=16.9.0"
}
},
"node_modules/@vscode/windows-ca-certs": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@vscode/windows-ca-certs/-/windows-ca-certs-0.3.1.tgz",
@@ -8022,6 +8085,12 @@
"node": ">=0.8.x"
}
},
"node_modules/exenv-es6": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/exenv-es6/-/exenv-es6-1.1.1.tgz",
"integrity": "sha512-vlVu3N8d6yEMpMsEm+7sUBAI81aqYYuEvfK0jNqmdb/OPXzzH7QWDDnVjMvDSY47JdHEqx/dfC/q8WkfoTmpGQ==",
"license": "MIT"
},
"node_modules/expand-brackets": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
@@ -14202,6 +14271,15 @@
"es5-ext": "~0.10.2"
}
},
"node_modules/lucide-react": {
"version": "0.460.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.460.0.tgz",
"integrity": "sha512-BVtq/DykVeIvRTJvRAgCsOwaGL8Un3Bxh8MbDxMhEWlZay3T4IpEKDEpwt5KZ0KJMHzgm6jrltxlT5eXOWXDHg==",
"license": "ISC",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
}
},
"node_modules/make-dir": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
@@ -20392,6 +20470,12 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/tabbable": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-5.3.3.tgz",
"integrity": "sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA==",
"license": "MIT"
},
"node_modules/table": {
"version": "5.4.6",
"resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz",
@@ -21271,8 +21355,7 @@
"node_modules/tslib": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
"integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==",
"dev": true
"integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ=="
},
"node_modules/tsscmp": {
"version": "1.0.6",
+2
View File
@@ -85,6 +85,7 @@
"@vscode/sudo-prompt": "9.3.1",
"@vscode/tree-sitter-wasm": "^0.0.4",
"@vscode/vscode-languagedetection": "1.0.21",
"@vscode/webview-ui-toolkit": "^1.4.0",
"@vscode/windows-mutex": "^0.5.0",
"@vscode/windows-process-tree": "^0.6.0",
"@vscode/windows-registry": "^1.1.0",
@@ -100,6 +101,7 @@
"https-proxy-agent": "^7.0.2",
"jschardet": "3.1.3",
"kerberos": "2.1.1",
"lucide-react": "^0.460.0",
"minimist": "^1.2.6",
"native-is-elevated": "0.7.0",
"native-keymap": "^3.3.5",
+18 -1
View File
@@ -121,6 +121,8 @@ import { normalizeNFC } from '../../base/common/normalization.js';
import { ICSSDevelopmentService, CSSDevelopmentService } from '../../platform/cssDev/node/cssDevService.js';
import { ExtensionSignatureVerificationService, IExtensionSignatureVerificationService } from '../../platform/extensionManagement/node/extensionSignatureVerificationService.js';
import { LLMMessageChannel } from '../../platform/void/electron-main/llmMessageChannel.js';
/**
* The main VS Code application. There will only ever be one instance,
* even if the user starts many instances (e.g. from the command line).
@@ -148,7 +150,7 @@ export class CodeApplication extends Disposable {
@IStateService private readonly stateService: IStateService,
@IFileService private readonly fileService: IFileService,
@IProductService private readonly productService: IProductService,
@IUserDataProfilesMainService private readonly userDataProfilesMainService: IUserDataProfilesMainService
@IUserDataProfilesMainService private readonly userDataProfilesMainService: IUserDataProfilesMainService,
) {
super();
@@ -508,6 +510,16 @@ export class CodeApplication extends Disposable {
});
//#endregion
// //#region Void IPC
// validatedIpcMain.handle('vscode:sendLLMMessage', async (event, data) => {
// try {
// await this.sendLLMMessage(data);
// } catch (error) {
// console.error('Error sending LLM message:', error);
// }
// });
// //#endregion
}
private onUnexpectedError(error: Error): void {
@@ -1225,6 +1237,11 @@ export class CodeApplication extends Disposable {
mainProcessElectronServer.registerChannel('logger', loggerChannel);
sharedProcessClient.then(client => client.registerChannel('logger', loggerChannel));
// Void
// const sendLLMMessageChannel = ProxyChannel.fromService(accessor.get(ISendLLMMessageService), disposables);
const sendLLMMessageChannel = new LLMMessageChannel();
mainProcessElectronServer.registerChannel('void-channel-sendLLMMessage', sendLLMMessageChannel);
// Extension Host Debug Broadcasting
const electronExtensionHostDebugBroadcastChannel = new ElectronExtensionHostDebugBroadcastChannel(accessor.get(IWindowsMainService));
mainProcessElectronServer.registerChannel('extensionhostdebugservice', electronExtensionHostDebugBroadcastChannel);
@@ -12,7 +12,7 @@ export interface IInlineDiffService {
removeDiffs(editor: ICodeEditor): void;
}
export const IInlineDiffService = createDecorator<IInlineDiffService>('inlineDiffService');
export const IInlineDiffService = createDecorator<IInlineDiffService>('inlineDiffServiceOld');
class InlineDiffService extends Disposable implements IInlineDiffService {
private readonly _diffDecorations = new Map<ICodeEditor, string[]>();
@@ -0,0 +1,105 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPLv3 License.
*--------------------------------------------------------------------------------------------*/
import { ProxyOnTextPayload, ProxyOnErrorPayload, ProxyOnFinalMessagePayload, LLMMessageServiceParams, ProxyLLMMessageParams, ProxyLLMMessageAbortParams } from '../common/llmMessageTypes.js';
import { IChannel } from '../../../base/parts/ipc/common/ipc.js';
import { IMainProcessService } from '../../ipc/common/mainProcessService.js';
import { InstantiationType, registerSingleton } from '../../instantiation/common/extensions.js';
import { generateUuid } from '../../../base/common/uuid.js';
import { createDecorator } from '../../instantiation/common/instantiation.js';
import { Event } from '../../../base/common/event.js';
import { IDisposable } from '../../../base/common/lifecycle.js';
// BROWSER IMPLEMENTATION OF SENDLLMMESSAGE
export const ISendLLMMessageService = createDecorator<ISendLLMMessageService>('sendLLMMessageService');
// defines an interface that node/ creates and browser/ uses
export interface ISendLLMMessageService {
readonly _serviceBrand: undefined;
sendLLMMessage: (params: LLMMessageServiceParams) => string;
abort: (requestId: string) => void;
}
export class SendLLMMessageService implements ISendLLMMessageService {
readonly _serviceBrand: undefined;
private readonly channel: IChannel;
private readonly _disposablesOfRequestId: Record<string, IDisposable[]> = {}
constructor(
@IMainProcessService mainProcessService: IMainProcessService // used as a renderer (only usable on client side)
) {
this.channel = mainProcessService.getChannel('void-channel-sendLLMMessage')
// const service = ProxyChannel.toService<LLMMessageChannel>(mainProcessService.getChannel('void-channel-sendLLMMessage')); // lets you call it like a service, not needed here
}
_addDisposable(requestId: string, disposable: IDisposable) {
if (!this._disposablesOfRequestId[requestId]) {
this._disposablesOfRequestId[requestId] = []
}
this._disposablesOfRequestId[requestId].push(disposable)
}
sendLLMMessage(params: LLMMessageServiceParams) {
const requestId_ = generateUuid();
const { onText, onFinalMessage, onError, ...proxyParams } = params;
// listen for listenerName='onText' | 'onFinalMessage' | 'onError', and call the original function on it
const onTextEvent: Event<ProxyOnTextPayload> = this.channel.listen('onText')
this._addDisposable(requestId_,
onTextEvent(e => {
if (requestId_ !== e.requestId) return;
onText(e)
})
)
const onFinalMessageEvent: Event<ProxyOnFinalMessagePayload> = this.channel.listen('onFinalMessage')
this._addDisposable(requestId_,
onFinalMessageEvent(e => {
if (requestId_ !== e.requestId) return;
onFinalMessage(e)
this._dispose(requestId_)
})
)
const onErrorEvent: Event<ProxyOnErrorPayload> = this.channel.listen('onError')
this._addDisposable(requestId_,
onErrorEvent(e => {
if (requestId_ !== e.requestId) return;
console.log('event onError', JSON.stringify(e))
onError(e)
this._dispose(requestId_)
})
)
// params will be stripped of all its functions
this.channel.call('sendLLMMessage', { ...proxyParams, requestId: requestId_ } satisfies ProxyLLMMessageParams);
return requestId_
}
private _dispose(requestId: string) {
if (!(requestId in this._disposablesOfRequestId)) return
for (const disposable of this._disposablesOfRequestId[requestId]) {
disposable.dispose()
}
delete this._disposablesOfRequestId[requestId]
}
abort(requestId: string) {
this.channel.call('abort', { requestId } satisfies ProxyLLMMessageAbortParams);
this._dispose(requestId)
}
}
registerSingleton(ISendLLMMessageService, SendLLMMessageService, InstantiationType.Delayed);
@@ -0,0 +1,58 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPLv3 License.
*--------------------------------------------------------------------------------------------*/
import { VoidConfig } from '../../../workbench/contrib/void/browser/registerConfig.js';
// ---------- type definitions ----------
export type OnText = (p: { newText: string, fullText: string }) => void
export type OnFinalMessage = (p: { fullText: string }) => void
export type OnError = (p: { error: Error | string }) => void
export type AbortRef = { current: (() => void) | null }
export type LLMMessage = {
role: 'system' | 'user' | 'assistant';
content: string;
}
export type LLMMessageServiceParams = {
onText: OnText;
onFinalMessage: OnFinalMessage;
onError: OnError;
messages: LLMMessage[];
voidConfig: VoidConfig | null;
logging: {
loggingName: string,
};
}
export type SendLLMMMessageParams = {
onText: OnText;
onFinalMessage: OnFinalMessage;
onError: OnError;
messages: LLMMessage[];
voidConfig: VoidConfig | null;
logging: {
loggingName: string,
};
abortRef: AbortRef;
}
// can't send functions across a proxy, use listeners instead
export const listenerNames = ['onText', 'onFinalMessage', 'onError'] as const
export type ProxyLLMMessageParams = Omit<LLMMessageServiceParams, typeof listenerNames[number]> & { requestId: string }
export type ProxyOnTextPayload = Parameters<OnText>[0] & { requestId: string }
export type ProxyOnFinalMessagePayload = Parameters<OnFinalMessage>[0] & { requestId: string }
export type ProxyOnErrorPayload = Parameters<OnError>[0] & { requestId: string }
export type ProxyLLMMessageAbortParams = { requestId: string }
@@ -0,0 +1,94 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPLv3 License.
*--------------------------------------------------------------------------------------------*/
// this channel is registered in `app.ts`
// code convention is to make a service responsible for this stuff, and not a channel, but this is simpler.
// you could create one instance in electron-main/my-service.ts and one in browser/my-service.ts (and define the interface IMyService in common/my-service.ts), but we just use a channel here
// registerSingleton(ISendLLMMessageService, SendLLMMessageService, InstantiationType.Delayed);
import { IServerChannel } from '../../../base/parts/ipc/common/ipc.js';
import { Emitter, Event } from '../../../base/common/event.js';
import { sendLLMMessage } from '../../../workbench/contrib/void/browser/react/out/util/sendLLMMessage.js';
import { listenerNames, ProxyOnTextPayload, ProxyOnErrorPayload, ProxyOnFinalMessagePayload, ProxyLLMMessageParams, AbortRef, SendLLMMMessageParams, ProxyLLMMessageAbortParams } from '../common/llmMessageTypes.js';
// NODE IMPLEMENTATION OF SENDLLMMESSAGE - calls sendLLMMessage() and returns listeners
export class LLMMessageChannel implements IServerChannel {
private readonly _onText = new Emitter<ProxyOnTextPayload>();
readonly onText = this._onText.event;
private readonly _onFinalMessage = new Emitter<ProxyOnFinalMessagePayload>();
readonly onFinalMessage = this._onFinalMessage.event;
private readonly _onError = new Emitter<ProxyOnErrorPayload>();
readonly onError = this._onError.event;
private readonly _abortRefOfRequestId: Record<string, AbortRef> = {}
constructor() { }
// browser uses this to listen for changes
listen(_: unknown, event: typeof listenerNames[number]): Event<any> {
if (event === 'onText') {
return this.onText;
}
else if (event === 'onFinalMessage') {
return this.onFinalMessage;
}
else if (event === 'onError') {
return this.onError;
}
else {
throw new Error(`Event not found: ${event}`);
}
}
// browser uses this to call
async call(_: unknown, command: string, params: any): Promise<any> {
try {
if (command === 'sendLLMMessage') {
this._callSendLLMMessage(params)
}
else if (command === 'abort') {
this._callAbort(params)
}
else {
throw new Error(`Void sendLLM: command "${command}" not recognized.`)
}
}
catch (e) {
console.log('llmMessageChannel: Call Error:', e)
}
}
// the only place sendLLMMessage is actually called
private _callSendLLMMessage(params: ProxyLLMMessageParams) {
const { requestId } = params;
if (!(requestId in this._abortRefOfRequestId))
this._abortRefOfRequestId[requestId] = { current: null }
const mainThreadParams: SendLLMMMessageParams = {
...params,
onText: ({ newText, fullText }) => { this._onText.fire({ requestId, newText, fullText }); },
onFinalMessage: ({ fullText }) => { this._onFinalMessage.fire({ requestId, fullText }); },
onError: ({ error }) => { this._onError.fire({ requestId, error }); },
abortRef: this._abortRefOfRequestId[requestId],
}
sendLLMMessage(mainThreadParams);
}
private _callAbort(params: ProxyLLMMessageAbortParams) {
const { requestId } = params;
if (!(requestId in this._abortRefOfRequestId)) return
this._abortRefOfRequestId[requestId].current?.()
delete this._abortRefOfRequestId[requestId]
}
}
@@ -741,8 +741,24 @@ export class CodeWindow extends BaseWindow implements ICodeWindow {
cb({ cancel: false, requestHeaders: Object.assign(details.requestHeaders, headers) });
});
// // Void: send from https://
// this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, async (details, cb) => {
// // const voidConfig = this.voidConfigStateService.state.voidConfig
// // const whichApi = voidConfig.default['whichApi']
// const endpoint = 'http://127.' //string | undefined = voidConfig[whichApi as VoidConfigField].endpoint
// if (endpoint && details.url.startsWith(endpoint)) {
// details.requestHeaders['Origin'] = 'https://app.voideditor.com'
// }
// cb({ cancel: false, requestHeaders: details.requestHeaders });
// });
}
private marketplaceHeadersPromise: Promise<object> | undefined;
private getMarketplaceHeaders(): Promise<object> {
if (!this.marketplaceHeadersPromise) {
@@ -0,0 +1,18 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPLv3 License.
*--------------------------------------------------------------------------------------------*/
import { OperatingSystem, OS } from '../../../../base/common/platform.js';
export function getCmdKey(): string {
if (OS === OperatingSystem.Macintosh) {
return '⌘';
} else {
return 'Ctrl';
}
}
@@ -1,66 +0,0 @@
// This is from the old repo
// const tailwindcss = require('tailwindcss')
// const autoprefixer = require('autoprefixer')
// const postcss = require('postcss')
// const fs = require('fs')
// const convertTailwindToCSS = ({ from, to }) => {
// console.log('converting ', from, ' --> ', to)
// const original_css_contents = fs.readFileSync(from, 'utf8')
// return postcss([
// tailwindcss, // this compiles tailwind of all the files specified in tailwind.config.json
// autoprefixer,
// ])
// .process(original_css_contents, { from, to })
// .then(processed_css_contents => { fs.writeFileSync(to, processed_css_contents.css) })
// .catch(error => {
// console.error('Error in build-css:', error)
// })
// }
// const esbuild = require('esbuild')
// const convertTSXtoJS = async ({ from, to }) => {
// console.log('converting ', from, ' --> ', to)
// return esbuild.build({
// entryPoints: [from],
// bundle: true,
// minify: true,
// sourcemap: true,
// outfile: to,
// format: 'iife', // apparently iife is safe for browsers (safer than cjs)
// platform: 'browser',
// external: ['vscode'],
// }).catch(() => process.exit(1));
// }
// (async () => {
// // convert tsx to js
// await convertTSXtoJS({
// from: 'src/webviews/sidebar/index.tsx',
// to: 'dist/webviews/sidebar/index.js',
// })
// await convertTSXtoJS({
// from: 'src/webviews/ctrlk/index.tsx',
// to: 'dist/webviews/ctrlk/index.js',
// })
// await convertTSXtoJS({
// from: 'src/webviews/diffline/index.tsx',
// to: 'dist/webviews/diffline/index.js',
// })
// // convert tailwind to css
// await convertTailwindToCSS({
// from: 'src/webviews/styles.css',
// to: 'dist/webviews/styles.css',
// })
// })()
@@ -1,168 +0,0 @@
{
"name": "void",
"publisher": "void",
"displayName": "Void",
"description": "",
"version": "0.0.1",
"engines": {
"vscode": "*"
},
"categories": [
"Other"
],
"enabledApiProposals": [
"editorInsets"
],
"activationEvents": [],
"main": "./out/extension/extension.js",
"contributes": {
"configuration": {
"title": "Void",
"properties": {}
},
"commands": [
{
"command": "typeInspector.inspect",
"title": "Inspect Types of All Variables"
},
{
"command": "void.ctrl+l",
"title": "Show Sidebar"
},
{
"command": "void.ctrl+k",
"title": "Make Inline Edit"
},
{
"command": "void.acceptDiff",
"title": "Approve Diff"
},
{
"command": "void.rejectDiff",
"title": "Discard Diff"
},
{
"command": "void.startNewThread",
"title": "Start a new chat",
"icon": "$(add)"
},
{
"command": "void.toggleThreadSelector",
"title": "View past chats",
"icon": "$(history)"
},
{
"command": "void.toggleSettings",
"title": "Void settings",
"icon": "$(settings-gear)"
}
],
"viewsContainers": {
"activitybar": [
{
"id": "voidViewContainer",
"title": "Chat",
"icon": "$(hubot)"
}
]
},
"views": {
"voidViewContainer": [
{
"type": "webview",
"id": "void.viewnumberone",
"name": "Void"
}
]
},
"keybindings": [
{
"command": "void.ctrl+l",
"key": "ctrl+l",
"mac": "cmd+l"
},
{
"command": "void.ctrl+k",
"key": "ctrl+k",
"mac": "cmd+k"
}
],
"menus": {
"view/title": [
{
"command": "void.startNewThread",
"when": "view == 'void.viewnumberone'",
"group": "navigation"
},
{
"command": "void.toggleThreadSelector",
"when": "view == 'void.viewnumberone'",
"group": "navigation"
},
{
"command": "void.toggleSettings",
"when": "view == 'void.viewnumberone'",
"group": "navigation"
}
]
}
},
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./",
"build": "rimraf dist && node build/build.js",
"pretest": "tsc -p ./ && eslint src --ext ts",
"test": "vscode-test"
},
"devDependencies": {
"@anthropic-ai/sdk": "^0.31.0",
"@eslint/js": "^9.9.1",
"@google/generative-ai": "^0.21.0",
"@monaco-editor/react": "^4.6.0",
"@rrweb/types": "^2.0.0-alpha.17",
"@types/diff": "^5.2.2",
"@types/diff-match-patch": "^1.0.36",
"@types/jest": "^29.5.12",
"@types/lodash": "^4.17.12",
"@types/mocha": "^10.0.8",
"@types/node": "^22.5.1",
"@types/react": "^18.3.4",
"@types/react-dom": "^18.3.0",
"@types/react-syntax-highlighter": "^15.5.13",
"@types/uuid": "^10.0.0",
"@typescript-eslint/eslint-plugin": "^8.3.0",
"@typescript-eslint/parser": "^8.3.0",
"@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",
"lodash": "^4.17.21",
"marked": "^14.1.0",
"ollama": "^0.5.9",
"openai": "^4.70.2",
"postcss": "^8.4.41",
"posthog-js": "^1.176.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-markdown": "^9.0.1",
"react-syntax-highlighter": "^15.6.1",
"rimraf": "^6.0.1",
"rrweb-snapshot": "^2.0.0-alpha.4",
"tailwindcss": "^3.4.10",
"typescript": "5.5.4",
"typescript-eslint": "^8.3.0",
"uuid": "^10.0.0"
},
"dependencies": {
"lru-cache": "^11.0.2",
"tree-sitter": "^0.21.1",
"tree-sitter-javascript": "^0.23.1",
"tree-sitter-python": "^0.23.4"
}
}
@@ -5,6 +5,6 @@ A couple things to remember:
- Make sure to add .js at the end of any external imports used in here, e.g. ../../../../../my_file.js. If you don't do this, you will get untraceable errors.
- src/ needs to be shallow so the detection of externals works properly (see tsup.config.js).
- src/ needs to be shallow (1 folder deep) so the detection of externals works properly (see tsup.config.js).
@@ -10,3 +10,4 @@ execSync('npx scope-tailwind ./src -o src2/ -s void-scope -c styles.css -p "pref
execSync('npx tsup')
console.log('✅ Done building! Press Cmd+Shift+B again.')
@@ -6,7 +6,6 @@ import React, { FormEvent, Fragment, useCallback, useEffect, useRef, useState }
import { useConfigState, useService, useThreadsState } from '../util/services.js';
import { sendLLMMessage } from '../util/sendLLMMessage.js';
import { generateDiffInstructions } from '../../../prompt/systemPrompts.js';
import { userInstructionsStr } from '../../../prompt/stringifyFiles.js';
import { CodeSelection, CodeStagingSelection } from '../../../registerThreads.js';
@@ -17,8 +16,10 @@ import { IModelService } from '../../../../../../../editor/common/services/model
import { URI } from '../../../../../../../base/common/uri.js';
import { EndOfLinePreference } from '../../../../../../../editor/common/model.js';
import { IDisposable } from '../../../../../../../base/common/lifecycle.js';
import { ErrorDisplay } from '../util/ErrorDisplay.js';
import { LLMMessageServiceParams } from '../../../../../../../platform/void/common/llmMessageTypes.js';
// import { } from '@vscode/webview-ui-toolkit/react';
// read files from VSCode
const VSReadFile = async (modelService: IModelService, uri: URI): Promise<string | null> => {
@@ -174,11 +175,11 @@ export const SidebarChat = () => {
// state of chat
const [messageStream, setMessageStream] = useState('')
const [isLoading, setIsLoading] = useState(false)
const abortFnRef = useRef<(() => void) | null>(null)
const [latestError, setLatestError] = useState('')
const latestRequestIdRef = useRef<string | null>(null)
const [latestError, setLatestError] = useState<Error | string | null>(null)
const sendLLMMessageService = useService('sendLLMMessageService')
const isDisabled = !instructions
@@ -209,11 +210,12 @@ export const SidebarChat = () => {
// send message to LLM
sendLLMMessage({
const object: LLMMessageServiceParams = {
logging: { loggingName: 'Chat' },
messages: [...(currentThread?.messages ?? []).map(m => ({ role: m.role, content: m.content })),],
onText: (newText, fullText) => setMessageStream(fullText),
onFinalMessage: (content) => {
onText: ({ newText, fullText }) => setMessageStream(fullText),
onFinalMessage: ({ fullText: content }) => {
console.log('chat: running final message')
// add assistant's message to chat history, and clear selection
@@ -222,8 +224,8 @@ export const SidebarChat = () => {
setMessageStream('')
setIsLoading(false)
},
onError: (error) => {
console.log('chat: running error')
onError: ({ error }) => {
console.log('chat: running error', error)
// add assistant's message to chat history, and clear selection
let content = messageStream; // just use the current content
@@ -236,21 +238,24 @@ export const SidebarChat = () => {
setLatestError(error)
},
voidConfig,
abortRef: abortFnRef,
})
}
const latestRequestId = sendLLMMessageService.sendLLMMessage(object)
latestRequestIdRef.current = latestRequestId
setIsLoading(true)
setInstructions('');
formRef.current?.reset(); // reset the form's text when clear instructions or unexpected behavior happens
threadsStateService.setStaging([]) // clear staging
setLatestError('')
setLatestError(null)
}
const onAbort = () => {
// abort claude
abortFnRef.current?.()
// abort the LLM
if (latestRequestIdRef.current)
sendLLMMessageService.abort(latestRequestIdRef.current)
// if messageStream was not empty, add it to the history
const llmContent = messageStream || '(null)'
@@ -338,9 +343,11 @@ export const SidebarChat = () => {
</div>
{/* error message */}
{!latestError ? null : <div>
{latestError}
</div>}
{latestError === null ? null :
<ErrorDisplay
error={latestError}
onDismiss={() => { setLatestError(null) }}
/>}
</div>
</>
}
@@ -109,13 +109,6 @@ export const SidebarSettings = () => {
))}
</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>
)
}
@@ -0,0 +1,161 @@
import React, { useState } from 'react';
import { AlertCircle, ChevronDown, ChevronUp, X } from 'lucide-react';
import { getCmdKey } from '../../../getCmdKey.js';
// const opaqueMessage = `\
// Unfortunately, Void can't see the full error. However, you should be able to find more details by pressing ${getCmdKey()}+Shift+P, typing "Toggle Developer Tools", and looking at the console.\n
// This error often means you have an incorrect API key. If you're self-hosting your own server, it might mean your CORS headers are off, and you should make sure your server's response has the header "Access-Control-Allow-Origins" set to "*", or at least allows "vscode-file://vscode-app".`
// if ((error instanceof Error) && (error.cause + '').includes('TypeError: Failed to fetch')) {
// e = error as any
// e['Void Team'] = opaqueMessage
// }
type Details = {
message: string,
name: string,
stack: string | null,
cause: string | null,
code: string | null,
additional: Record<string, any>
}
// Get detailed error information
const getErrorDetails = (error: unknown) => {
let details: Details;
let e: Error & { [other: string]: undefined | any }
// If fetch() fails, it gives an opaque message. We add extra details to the error.
if (error instanceof Error) {
e = error
}
// sometimes error is an object but not an Error
else if (typeof error === 'object') {
e = new Error(`The server didn't give a very useful error message. More details below.`, { cause: JSON.stringify(error) })
}
else {
e = new Error(String(error))
}
// console.log('error display', JSON.stringify(e))
const message = e.message && e.error ?
(e.message + ':\n' + e.error)
: e.message || e.error || JSON.stringify(error)
details = {
name: e.name || 'Error',
message: message,
stack: null, // e.stack is ignored because it's ugly and not very useful
cause: e.cause ? String(e.cause) : null,
code: e.code || null,
additional: {}
}
// Collect any additional properties from the e
for (let prop of Object.getOwnPropertyNames(e).filter((prop) => !Object.keys(details).includes(prop)))
details.additional[prop] = (e as any)[prop]
return details;
};
export const ErrorDisplay = ({
error,
onDismiss = null,
showDismiss = true,
className = ''
}: {
error: Error | object | string,
onDismiss: (() => void) | null,
showDismiss?: boolean,
className?: string
}) => {
const [isExpanded, setIsExpanded] = useState(false);
const details = getErrorDetails(error);
const hasDetails = details.cause || Object.keys(details.additional).length > 0;
return (
<div className={`rounded-lg border border-red-200 bg-red-50 p-4 ${className}`}>
{/* Header */}
<div className="flex items-start justify-between">
<div className="flex gap-3">
<AlertCircle className="h-5 w-5 text-red-500 mt-0.5" />
<div className="flex-1">
<h3 className="font-semibold text-red-800">
{details.name}
</h3>
<p className="text-red-700 mt-1">
{details.message}
</p>
</div>
</div>
<div className="flex gap-2">
{hasDetails && (
<button
onClick={() => setIsExpanded(!isExpanded)}
className="text-red-600 hover:text-red-800 p-1 rounded"
>
{isExpanded ? (
<ChevronUp className="h-5 w-5" />
) : (
<ChevronDown className="h-5 w-5" />
)}
</button>
)}
{showDismiss && onDismiss && (
<button
onClick={onDismiss}
className="text-red-600 hover:text-red-800 p-1 rounded"
>
<X className="h-5 w-5" />
</button>
)}
</div>
</div>
{/* Expandable Details */}
{isExpanded && hasDetails && (
<div className="mt-4 space-y-3 border-t border-red-200 pt-3">
{details.code && (
<div>
<span className="font-semibold text-red-800">Error Code: </span>
<span className="text-red-700">{details.code}</span>
</div>
)}
{details.cause && (
<div>
<span className="font-semibold text-red-800">Cause: </span>
<span className="text-red-700">{details.cause}</span>
</div>
)}
{Object.keys(details.additional).length > 0 && (
<div>
<span className="font-semibold text-red-800">Additional Information:</span>
<pre className="mt-1 text-sm text-red-700 overflow-x-auto whitespace-pre-wrap">
{Object.keys(details.additional).map(key => `${key}:\n${details.additional[key]}`).join('\n')}
</pre>
</div>
)}
{/* {details.stack && (
<div>
<span className="font-semibold text-red-800">Stack Trace:</span>
<pre className="mt-1 text-sm text-red-700 overflow-x-auto whitespace-pre-wrap">
{details.stack}
</pre>
</div>
)} */}
</div>
)}
</div>
);
};
@@ -1,8 +1,6 @@
import React, { useEffect, useState } from 'react';
import * as ReactDOM from 'react-dom/client'
import { ReactServicesType, VoidSidebarState } from '../../../registerSidebar.js';
import { ConfigState } from '../../../registerConfig.js';
import { ThreadsState } from '../../../registerThreads.js';
import { _registerServices } from './services.js';
@@ -4,45 +4,19 @@ import { Ollama } from 'ollama/browser'
import { Content, GoogleGenerativeAI, GoogleGenerativeAIFetchError } from '@google/generative-ai';
import { posthog } from 'posthog-js'
import type { VoidConfig } from '../../../registerConfig.js';
export type AbortRef = { current: (() => void) | null }
export type OnText = (newText: string, fullText: string) => void
export type OnFinalMessage = (input: string) => void
export type LLMMessageAnthropic = {
role: 'user' | 'assistant';
content: string;
}
export type LLMMessage = {
role: 'system' | 'user' | 'assistant';
content: string;
}
import type { LLMMessage, OnText, OnError, OnFinalMessage, SendLLMMMessageParams, } from '../../../../../../../platform/void/common/llmMessageTypes.js';
import { LLMMessageServiceParams } from '../../../../../../../platform/void/common/llmMessageTypes.js';
type SendLLMMessageFnTypeInternal = (params: {
messages: LLMMessage[];
onText: OnText;
onFinalMessage: OnFinalMessage;
onError: (error: string) => void;
onError: OnError;
voidConfig: VoidConfig;
_setAborter: (aborter: () => void) => void;
}) => void
type SendLLMMessageFnTypeExternal = (params: {
messages: LLMMessage[];
onText: OnText;
onFinalMessage: (fullText: string) => void;
onError: (error: string) => void;
voidConfig: VoidConfig | null;
abortRef: AbortRef;
logging: {
loggingName: string,
};
}) => void
const parseMaxTokensStr = (maxTokensStr: string) => {
// parse the string but only if the full string is a valid number, eg parseInt('100abc') should return NaN
@@ -53,6 +27,10 @@ const parseMaxTokensStr = (maxTokensStr: string) => {
}
// Anthropic
type LLMMessageAnthropic = {
role: 'user' | 'assistant';
content: string;
}
const sendAnthropicMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter }) => {
const anthropic = new Anthropic({ apiKey: voidConfig.anthropic.apikey, dangerouslyAllowBrowser: true }); // defaults to process.env["ANTHROPIC_API_KEY"]
@@ -76,23 +54,23 @@ const sendAnthropicMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFi
// when receive text
stream.on('text', (newText, fullText) => {
onText(newText, fullText)
onText({ newText, fullText })
})
// when we get the final message on this stream (or when error/fail)
stream.on('finalMessage', (claude_response) => {
// stringify the response's content
const content = claude_response.content.map(c => c.type === 'text' ? c.text : c.type).join('\n');
onFinalMessage(content)
onFinalMessage({ fullText: 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.')
onError({ error: 'Invalid API key.' })
}
else {
onError(error.message)
onError({ error })
}
})
@@ -131,21 +109,16 @@ const sendGeminiMsg: SendLLMMessageFnTypeInternal = async ({ messages, onText, o
for await (const chunk of response.stream) {
const newText = chunk.text();
fullText += newText;
onText(newText, fullText);
onText({ newText, fullText });
}
onFinalMessage(fullText);
onFinalMessage({ fullText });
})
.catch((error) => {
if (error instanceof GoogleGenerativeAIFetchError) {
if (error.status === 400) {
onError('Invalid API key.');
}
else {
onError(`${error.name}:\n${error.message}`);
}
if (error instanceof GoogleGenerativeAIFetchError && error.status === 400) {
onError({ error: 'Invalid API key.' });
}
else {
onError(error);
onError({ error });
}
})
}
@@ -191,22 +164,17 @@ const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinal
for await (const chunk of response) {
const newText = chunk.choices[0]?.delta?.content || '';
fullText += newText;
onText(newText, fullText);
onText({ newText, fullText });
}
onFinalMessage(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.');
}
else {
onError(`${error.name}:\n${error.message}`);
}
if (error instanceof OpenAI.APIError && error.status === 401) {
onError({ error: 'Invalid API key.' });
}
else {
onError(error);
onError({ error });
}
})
@@ -231,14 +199,14 @@ export const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({ messages, onText,
for await (const chunk of stream) {
const newText = chunk.message.content;
fullText += newText;
onText(newText, fullText);
onText({ newText, fullText });
}
onFinalMessage(fullText);
onFinalMessage({ fullText });
})
// when error/fail
.catch(error => {
onError(error)
onError({ error })
})
};
@@ -281,24 +249,24 @@ const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFin
// when receive text
if (type === 'message') {
fullText += message
onText(message, fullText)
onText({ newText: message, fullText })
}
else if (type === 'sources') {
const { filepath, linestart: _, lineend: _2 } = message as { filepath: string; linestart: number | null; lineend: number | null }
fullText += filepath
onText(filepath, fullText)
onText({ newText: filepath, fullText })
}
// type: 'status' with an empty 'message' means last message
else if (type === 'status') {
if (!message) {
onFinalMessage(fullText)
onFinalMessage({ fullText })
}
}
}
})
.catch(e => {
onError(e)
.catch(error => {
onError({ error })
});
}
@@ -307,7 +275,8 @@ const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFin
export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({
export const sendLLMMessage = ({
messages,
onText: onText_,
onFinalMessage: onFinalMessage_,
@@ -315,7 +284,7 @@ export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({
abortRef: abortRef_,
voidConfig,
logging: { loggingName }
}) => {
}: SendLLMMMessageParams) => {
if (!voidConfig) return;
// trim message content (Anthropic and other providers give an error if there is trailing whitespace)
@@ -338,22 +307,23 @@ export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({
let _setAborter = (fn: () => void) => { _aborter = fn }
let _didAbort = false
const onText = (newText: string, fullText: string) => {
const onText: OnText = ({ newText, fullText }) => {
if (_didAbort) return
onText_(newText, fullText)
onText_({ newText, fullText })
_fullTextSoFar = fullText
}
const onFinalMessage = (fullText: string) => {
const onFinalMessage: OnFinalMessage = ({ fullText }) => {
if (_didAbort) return
captureChatEvent(`${loggingName} - Received Full Message`, { messageLength: fullText.length, duration: new Date().getMilliseconds() - submit_time.getMilliseconds() })
onFinalMessage_(fullText)
onFinalMessage_({ fullText })
}
const onError = (error: string) => {
const onError: OnError = ({ error }) => {
console.error('sendLLMMessage onError:', error)
if (_didAbort) return
captureChatEvent(`${loggingName} - Error`, { error })
onError_(error)
onError_({ error })
}
const onAbort = () => {
@@ -385,14 +355,15 @@ export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({
sendGreptileMsg({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter, });
break;
default:
onError(`Error: whichApi was ${voidConfig.default.whichApi}, which is not recognized!`)
onError({ error: `Error: whichApi was ${voidConfig.default.whichApi}, which is not recognized!` })
break;
}
}
catch (e) {
onError(`Unexpected Error in sendLLMMessage: ${e}`);
(_aborter as any)?.()
catch (error) {
if (error instanceof Error) { onError({ error }) }
else { onError({ error: `Unexpected Error in sendLLMMessage: ${error}` }); }
; (_aborter as any)?.()
_didAbort = true
}
@@ -580,7 +551,7 @@ export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({
// export type AbortRef = { current: (() => void) }
// export type OnText = (newText: string, fullText: string) => void
// export type LLMMessageOnText = (newText: string, fullText: string) => void
// export type OnFinalMessage = (input: string) => void
@@ -600,7 +571,7 @@ export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({
// mode: 'chat' | 'fim',
// messages: LLMMessage[],
// options?: LLMMessageOptions,
// onText: OnText,
// onText: LLMMessageOnText,
// onFinalMessage: OnFinalMessage,
// onError: (error: string) => void,
// abortRef: AbortRef,
@@ -613,7 +584,7 @@ export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({
// | { mode: 'fim', messages?: undefined, fimInfo: FimInfo, }
// ) & {
// options?: LLMMessageOptions,
// onText: OnText,
// onText: LLMMessageOnText,
// onFinalMessage: OnFinalMessage,
// onError: (error: string) => void,
// abortRef: AbortRef,
@@ -5,12 +5,15 @@ import { ThreadsState } from '../../../registerThreads.js'
// normally to do this you'd use a useEffect that calls .onDidChangeState(), but useEffect mounts too late and misses initial state changes
let services: ReactServicesType
// even if React hasn't mounted yet, these variables are always updated to the latest state:
let sidebarState: VoidSidebarState
let configState: ConfigState
let threadsState: ThreadsState
// React listens by adding a setState function to these:
const sidebarStateListeners: Set<(s: VoidSidebarState) => void> = new Set()
const configStateListeners: Set<(s: ConfigState) => void> = new Set()
const threadsStateListeners: Set<(s: ThreadsState) => void> = new Set()
@@ -15,7 +15,7 @@ export default defineConfig({
// sourcemap: true,
clean: true,
platform: 'browser',
platform: 'browser', // 'node'
target: 'esnext',
injectStyle: true, // bundle css into the output file
outExtension: () => ({ js: '.js' }),
@@ -147,7 +147,7 @@ registerAction2(class extends Action2 {
}
async run(accessor: ServicesAccessor): Promise<void> {
const stateService = accessor.get(IVoidSidebarStateService)
stateService.setState({ isHistoryOpen: false, currentTab: 'settings' })
stateService.setState({ isHistoryOpen: false, currentTab: stateService.state.currentTab === 'settings' ? 'chat' : 'settings' })
stateService.fireBlurChat()
}
})
@@ -241,7 +241,7 @@ class VoidConfigStateService extends Disposable implements IVoidConfigStateServi
_serviceBrand: undefined;
private readonly _onDidChangeState = new Emitter<void>();
readonly onDidChangeState: Event<void> = this._onDidChangeState.event;
readonly onDidChangeState: Event<void> = this._onDidChangeState.event; // this is primarily for use in react, so react can listen + update on state changes
state: ConfigState;
readonly voidConfigInfo: VoidConfigInfo = voidConfigInfo; // just putting this here for simplicity, it's static though
@@ -10,7 +10,6 @@ import { ICodeEditor, IOverlayWidget, IViewZone } from '../../../../editor/brows
// import { IUndoRedoService } from '../../../../platform/undoRedo/common/undoRedo.js';
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
import { sendLLMMessage } from './react/out/util/sendLLMMessage.js';
// import { throttle } from '../../../../base/common/decorators.js';
import { IVoidConfigStateService } from './registerConfig.js';
import { writeFileWithDiffInstructions } from './prompt/systemPrompts.js';
@@ -29,6 +28,10 @@ import { ILanguageService } from '../../../../editor/common/languages/language.j
import * as dom from '../../../../base/browser/dom.js';
import { Widget } from '../../../../base/browser/ui/widget.js';
import { URI } from '../../../../base/common/uri.js';
import { LLMMessageServiceParams } from '../../../../platform/void/common/llmMessageTypes.js';
import { ISendLLMMessageService } from '../../../../platform/void/browser/llmMessageService.js';
// import { ISendLLMMessageService } from '../../../../platform/void/common/sendLLMMessage.js';
// import { sendLLMMessage } from './react/out/util/sendLLMMessage.js';
// gets converted to --vscode-void-greenBG, see void.css
@@ -115,7 +118,7 @@ export interface IInlineDiffsService {
}
export const IInlineDiffsService = createDecorator<IInlineDiffsService>('inlineDiffsService');
export const IInlineDiffsService = createDecorator<IInlineDiffsService>('inlineDiffAreasService');
class InlineDiffsService extends Disposable implements IInlineDiffsService {
_serviceBrand: undefined;
@@ -148,6 +151,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
@IModelService private readonly _modelService: IModelService,
@IUndoRedoService private readonly _undoRedoService: IUndoRedoService, // undoRedo service is the history of pressing ctrl+z
@ILanguageService private readonly _langService: ILanguageService,
@ISendLLMMessageService private readonly _sendLLMMessageService: ISendLLMMessageService,
) {
super();
@@ -727,20 +731,22 @@ Please finish writing the new file by applying the diff to the original file. Re
// <SUF>${suffix}</SUF>
// <MID>`;
const abortRef = { current: null } as { current: null | (() => void) }
await new Promise<void>((resolve, reject) => {
sendLLMMessage({
let streamRequestId: string | null = null
const object: LLMMessageServiceParams = {
logging: { loggingName: 'streamChunk' },
messages: [
{ role: 'system', content: writeFileWithDiffInstructions, },
// TODO include more context too
{ role: 'user', content: promptContent, }
],
onText: (newText: string, fullText: string) => {
onText: ({ newText, fullText }) => {
this._writeDiffAreaLLMText(diffArea, fullText)
this._refreshDiffsInURI(uri)
},
onFinalMessage: (fullText: string) => {
onFinalMessage: ({ fullText }) => {
this._writeText(uri, fullText,
{ startLineNumber: diffArea.startLine, startColumn: 1, endLineNumber: diffArea.endLine, endColumn: Number.MAX_SAFE_INTEGER }, // 1-indexed
)
@@ -751,13 +757,16 @@ Please finish writing the new file by applying the diff to the original file. Re
onError: (e: any) => {
console.error('Error rewriting file with diff', e);
// TODO indicate there was an error
abortRef.current?.()
if (streamRequestId)
this._sendLLMMessageService.abort(streamRequestId)
diffArea._sweepState = { isStreaming: false, line: null }
resolve();
},
voidConfig,
abortRef,
})
}
streamRequestId = this._sendLLMMessageService.sendLLMMessage(object)
})
onFinishEdit()
@@ -47,6 +47,9 @@ import { IVoidConfigStateService } from './registerConfig.js';
import { IFileService } from '../../../../platform/files/common/files.js';
import { IInlineDiffsService } from './registerInlineDiffs.js';
import { IModelService } from '../../../../editor/common/services/model.js';
import { ISendLLMMessageService } from '../../../../platform/void/browser/llmMessageService.js';
// import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';
@@ -65,6 +68,7 @@ export type ReactServicesType = {
fileService: IFileService;
modelService: IModelService;
inlineDiffService: IInlineDiffsService;
sendLLMMessageService: ISendLLMMessageService;
}
// ---------- Define viewpane ----------
@@ -109,6 +113,7 @@ class VoidSidebarViewPane extends ViewPane {
fileService: accessor.get(IFileService),
modelService: accessor.get(IModelService),
inlineDiffService: accessor.get(IInlineDiffsService),
sendLLMMessageService: accessor.get(ISendLLMMessageService),
}
mountFn(root, services);
});
@@ -328,6 +328,11 @@ export class DesktopMain extends Disposable {
//
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// // Void
// const sendLLMMessageService = new SendLLMMessageService();
// serviceCollection.set(ISendLLMMessageService, sendLLMMessageService);
return { serviceCollection, logService, storageService, configurationService };
}
+2 -1
View File
@@ -14,9 +14,10 @@ import './browser/workbench.contribution.js';
//#region --- void
//#region --- Void
// Void added this:
import './contrib/void/browser/void.contribution.js';
import '../platform/void/browser/llmMessageService.js';
//#endregion
@@ -31,6 +31,12 @@ import './electron-sandbox/parts/dialogs/dialog.contribution.js';
//#endregion
// //#region --- Void
// // Void added this (modeling off of import '.*clipboardservice.js'):
// import './services/void/electron-main/sendLLMMessage.js';
// //#endregion
//#region --- workbench services
@@ -32,6 +32,9 @@ import './browser/web.main.js';
//#endregion
//#region --- workbench services
import './services/integrity/browser/integrityService.js';
+12
View File
@@ -77,6 +77,18 @@
process.on(type, callback);
}
},
// Void : {
// /**
// * Send a message to the LLM.
// * @param {any} data The data to send to the LLM.
// * @returns {Promise<any>} The response from the LLM.
// */
// sendLLMMessage: async (data) => {
// // Use ipcRenderer.invoke to send the message to the main process (see app.ts)
// return await ipcRenderer.invoke('vscode:sendLLMMessage', data);
// }
// },
};
if (process.contextIsolated) {