Merge branch 'main' into re-add-autocomplete

This commit is contained in:
mp 2024-11-26 16:30:38 -08:00
parent 48426403f3
commit ef9ac7a9e3
30 changed files with 720 additions and 388 deletions

87
package-lock.json generated
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",

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",

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);

View file

@ -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[]>();

View file

@ -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);

View file

@ -0,0 +1,68 @@
/*---------------------------------------------------------------------------------------------
* 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 LLMMessageOptions = {
stopTokens?: string[],
prefix?: string,
suffix?: string,
}
export type LLMMessageServiceParams = {
onText: OnText;
onFinalMessage: OnFinalMessage;
onError: OnError;
messages: LLMMessage[];
voidConfig: VoidConfig | null;
logging: {
loggingName: string,
};
options: LLMMessageOptions;
}
export type SendLLMMMessageParams = {
onText: OnText;
onFinalMessage: OnFinalMessage;
onError: OnError;
messages: LLMMessage[];
voidConfig: VoidConfig | null;
logging: {
loggingName: string,
};
options: LLMMessageOptions;
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 }

View file

@ -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]
}
}

View file

@ -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) {

View file

@ -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';
}
}

View file

@ -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',
// })
// })()

View file

@ -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"
}
}

View file

@ -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).

View file

@ -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.')

View file

@ -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,22 +238,25 @@ export const SidebarChat = () => {
setLatestError(error)
},
voidConfig,
abortRef: abortFnRef,
options: {},
})
}
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)'
@ -339,9 +344,11 @@ export const SidebarChat = () => {
</div>
{/* error message */}
{!latestError ? null : <div>
{latestError}
</div>}
{latestError === null ? null :
<ErrorDisplay
error={latestError}
onDismiss={() => { setLatestError(null) }}
/>}
</div>
</>
}

View file

@ -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>
)
}

View file

@ -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>
);
};

View file

@ -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';

View file

@ -4,54 +4,21 @@ import { Ollama } from 'ollama/browser'
import { Content, GoogleGenerativeAI, GoogleGenerativeAIFetchError } from '@google/generative-ai';
import { posthog } from 'posthog-js'
import type { VoidConfig } from '../../../registerConfig.js';
import type { LLMMessage, OnText, OnError, OnFinalMessage, SendLLMMMessageParams, LLMMessageOptions, } from '../../../../../../../platform/void/common/llmMessageTypes.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;
}
export type LLMMessageOptions = {
stopTokens?: string[],
prefix?: string,
suffix?: string,
}
type SendLLMMessageFnTypeInternal = (params: {
messages: LLMMessage[];
options: LLMMessageOptions;
onText: OnText;
onFinalMessage: OnFinalMessage;
onError: (error: string) => void;
onError: OnError;
voidConfig: VoidConfig;
_setAborter: (aborter: () => void) => void;
}) => void
type SendLLMMessageFnTypeExternal = (params: {
messages: LLMMessage[];
options: LLMMessageOptions;
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
const int = isNaN(Number(maxTokensStr)) ? undefined : parseInt(maxTokensStr)
@ -61,6 +28,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"]
@ -84,23 +55,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 })
}
})
@ -139,21 +110,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 });
}
})
}
@ -199,22 +165,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 });
}
})
@ -233,19 +194,17 @@ export const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({ messages, options,
prompt: options.prefix ?? '',
suffix: options.suffix ?? '',
stream: true,
options: {
num_predict: parseMaxTokensStr(voidConfig.default.maxTokens),
stop: options.stopTokens,
}
options: { num_predict: parseMaxTokensStr(voidConfig.default.maxTokens), stop: options.stopTokens, }
})
.then(async stream => {
_setAborter(() => stream.abort())
// iterate through the stream
for await (const chunk of stream) {
const newText = chunk.response;
fullText += newText;
onText(newText, fullText);
onText({ newText, fullText });
}
onFinalMessage(fullText);
onFinalMessage({ fullText });
})
// when error/fail
@ -259,10 +218,7 @@ export const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({ messages, options,
model: voidConfig.ollama.model,
messages: messages,
stream: true,
options: {
num_predict: parseMaxTokensStr(voidConfig.default.maxTokens), // this is max_tokens
stop: options.stopTokens,
}
options: { num_predict: parseMaxTokensStr(voidConfig.default.maxTokens) } // this is max_tokens
})
.then(async stream => {
_setAborter(() => stream.abort())
@ -270,9 +226,9 @@ export const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({ messages, options,
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
@ -320,24 +276,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 })
});
}
@ -346,7 +302,8 @@ const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFin
export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({
export const sendLLMMessage = ({
messages,
options,
onText: onText_,
@ -354,8 +311,8 @@ export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({
onError: onError_,
abortRef: abortRef_,
voidConfig,
logging: { loggingName },
}) => {
logging: { loggingName }
}: SendLLMMMessageParams) => {
if (!voidConfig) return;
// set messages appropriately if fill in middle mode
@ -391,22 +348,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 = () => {
@ -438,14 +396,15 @@ export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({
sendGreptileMsg({ messages, options, 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
}
@ -589,7 +548,7 @@ ${suffix}
// 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
@ -609,7 +568,7 @@ ${suffix}
// mode: 'chat' | 'fim',
// messages: LLMMessage[],
// options?: LLMMessageOptions,
// onText: OnText,
// onText: LLMMessageOnText,
// onFinalMessage: OnFinalMessage,
// onError: (error: string) => void,
// abortRef: AbortRef,
@ -622,7 +581,7 @@ ${suffix}
// | { mode: 'fim', messages?: undefined, fimInfo: FimInfo, }
// ) & {
// options?: LLMMessageOptions,
// onText: OnText,
// onText: LLMMessageOnText,
// onFinalMessage: OnFinalMessage,
// onError: (error: string) => void,
// abortRef: AbortRef,

View file

@ -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()

View file

@ -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' }),

View file

@ -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()
}
})

View file

@ -8,12 +8,12 @@ import { ILanguageFeaturesService } from '../../../../editor/common/services/lan
import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js';
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
import { IVoidConfigStateService } from './registerConfig.js';
import { sendLLMMessage } from './react/out/util/sendLLMMessage.js';
import { ITextModel } from '../../../../editor/common/model.js';
import { Position } from '../../../../editor/common/core/position.js';
import { InlineCompletion, InlineCompletionContext } from '../../../../editor/common/languages.js';
import { CancellationToken } from '../../../../base/common/cancellation.js';
import { Range } from '../../../../editor/common/core/range.js';
import { ISendLLMMessageService } from '../../../../platform/void/browser/llmMessageService.js';
// The extension this was called from is here - https://github.com/voideditor/void/blob/autocomplete/extensions/void/src/extension/extension.ts
@ -138,10 +138,10 @@ type Autocompletion = {
suffix: string,
startTime: number,
endTime: number | undefined,
abortRef: { current: () => void },
status: AutocompletionStatus,
llmPromise: Promise<string> | undefined,
result: string,
requestId: string | null,
}
const DEBOUNCE_TIME = 500
@ -516,7 +516,8 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
this._autocompletionsOfDocument[docUriStr] = new LRUCache<number, Autocompletion>(
MAX_CACHE_SIZE,
(autocompletion: Autocompletion) => {
autocompletion.abortRef.current()
if (autocompletion.requestId)
this._sendLLMMessageService.abort(autocompletion.requestId)
}
)
}
@ -624,29 +625,29 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
suffix: suffix,
startTime: Date.now(),
endTime: undefined,
abortRef: { current: () => { } },
status: 'pending',
llmPromise: undefined,
result: '',
requestId: null,
}
// set parameters of `newAutocompletion` appropriately
newAutocompletion.llmPromise = new Promise((resolve, reject) => {
sendLLMMessage({
const requestId = this._sendLLMMessageService.sendLLMMessage({
logging: { loggingName: 'Autocomplete' },
messages: [],
options: { prefix, suffix, stopTokens, },
onText: async (tokenStr: string, completionStr: string) => {
onText: async ({ newText, fullText }) => {
newAutocompletion.result = completionStr
newAutocompletion.result = fullText
// if generation doesn't match the prefix for the first few tokens generated, reject it
if (!getPrefixAutocompletionMatch({ prefix: this._lastPrefix, autocompletion: newAutocompletion })) {
reject('LLM response did not match user\'s text.')
}
},
onFinalMessage: (finalMessage: string) => {
onFinalMessage: ({ fullText }) => {
// newAutocompletion.prefix = prefix
// newAutocompletion.suffix = suffix
@ -655,19 +656,19 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
// newAutocompletion.abortRef = { current: () => { } }
newAutocompletion.status = 'finished'
// newAutocompletion.promise = undefined
newAutocompletion.result = postprocessResult(extractCodeFromResult(finalMessage))
newAutocompletion.result = postprocessResult(extractCodeFromResult(fullText))
resolve(newAutocompletion.result)
},
onError: (e: any) => {
onError: ({ error }) => {
newAutocompletion.endTime = Date.now()
newAutocompletion.status = 'error'
reject(e)
reject(error)
},
voidConfig: this._voidConfigStateService.state.voidConfig,
abortRef: newAutocompletion.abortRef,
})
newAutocompletion.requestId = requestId
// if the request hasnt resolved in TIMEOUT_TIME seconds, reject it
setTimeout(() => {
@ -676,9 +677,10 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
}
}, TIMEOUT_TIME)
})
// add autocompletion to cache
this._autocompletionsOfDocument[docUriStr].set(newAutocompletion.id, newAutocompletion)
@ -703,6 +705,7 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
constructor(
@ILanguageFeaturesService private _langFeatureService: ILanguageFeaturesService,
@IVoidConfigStateService private readonly _voidConfigStateService: IVoidConfigStateService,
@ISendLLMMessageService private readonly _sendLLMMessageService: ISendLLMMessageService,
) {
super()

View file

@ -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

View file

@ -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();
@ -730,23 +734,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
)
@ -757,14 +760,17 @@ 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,
options: {},
})
options: {}
}
streamRequestId = this._sendLLMMessageService.sendLLMMessage(object)
})
onFinishEdit()

View file

@ -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);
});

View file

@ -328,6 +328,11 @@ export class DesktopMain extends Disposable {
//
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// // Void
// const sendLLMMessageService = new SendLLMMessageService();
// serviceCollection.set(ISendLLMMessageService, sendLLMMessageService);
return { serviceCollection, logService, storageService, configurationService };
}

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

View file

@ -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

View file

@ -32,6 +32,9 @@ import './browser/web.main.js';
//#endregion
//#region --- workbench services
import './services/integrity/browser/integrityService.js';

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) {