diff --git a/src/vs/platform/void/browser/llmMessageService.ts b/src/vs/platform/void/browser/llmMessageService.ts index 61f51ed8..79fbb138 100644 --- a/src/vs/platform/void/browser/llmMessageService.ts +++ b/src/vs/platform/void/browser/llmMessageService.ts @@ -27,16 +27,28 @@ export interface ISendLLMMessageService { export class SendLLMMessageService implements ISendLLMMessageService { readonly _serviceBrand: undefined; - private readonly channel: IChannel; + private readonly channel: IChannel // LLMMessageChannel private readonly _disposablesOfRequestId: Record = {} + private readonly onTextEvent: Event + private readonly onFinalMessageEvent: Event + private readonly onErrorEvent: Event constructor( @IMainProcessService mainProcessService: IMainProcessService // used as a renderer (only usable on client side) ) { + this.channel = mainProcessService.getChannel('void-channel-sendLLMMessage') - // const service = ProxyChannel.toService(mainProcessService.getChannel('void-channel-sendLLMMessage')); // lets you call it like a service, not needed here + + console.log('setting up IPC') + + // this sets up an IPC channel and takes a few ms, so should happen immediately + this.onTextEvent = this.channel.listen('onText') + this.onFinalMessageEvent = this.channel.listen('onFinalMessage') + this.onErrorEvent = this.channel.listen('onError') + + // const service = ProxyChannel.toService(mainProcessService.getChannel('void-channel-sendLLMMessage')); // lets you call it like a service } _addDisposable(requestId: string, disposable: IDisposable) { @@ -54,28 +66,26 @@ export class SendLLMMessageService implements ISendLLMMessageService { // listen for listenerName='onText' | 'onFinalMessage' | 'onError', and call the original function on it - const onTextEvent: Event = this.channel.listen('onText') this._addDisposable(requestId_, - onTextEvent(e => { + this.onTextEvent(e => { if (requestId_ !== e.requestId) return; onText(e) }) ) - const onFinalMessageEvent: Event = this.channel.listen('onFinalMessage') this._addDisposable(requestId_, - onFinalMessageEvent(e => { + this.onFinalMessageEvent(e => { if (requestId_ !== e.requestId) return; onFinalMessage(e) this._dispose(requestId_) }) ) - const onErrorEvent: Event = this.channel.listen('onError') this._addDisposable(requestId_, - onErrorEvent(e => { + this.onErrorEvent(e => { + console.log('sendLLMMessageService - error event received (havent checked req)') if (requestId_ !== e.requestId) return; - console.log('event onError', JSON.stringify(e)) + console.log('sendLLMMessageService - error event received', JSON.stringify(e)) onError(e) this._dispose(requestId_) }) diff --git a/src/vs/platform/void/common/configTypes.ts b/src/vs/platform/void/common/configTypes.ts index 134433ff..8a81841a 100644 --- a/src/vs/platform/void/common/configTypes.ts +++ b/src/vs/platform/void/common/configTypes.ts @@ -228,7 +228,7 @@ export type VoidProviderState = { type UnionOfKeys = T extends T ? keyof T : never; -type ProviderSettingName = UnionOfKeys +export type ProviderSettingName = UnionOfKeys @@ -361,5 +361,5 @@ type VoidFeatureState = { } | null, } export type FeatureName = keyof VoidFeatureState -export const featureNames = ['Ctrl+L', 'Ctrl+K', 'Autocomplete'] +export const featureNames = ['Ctrl+L', 'Ctrl+K', 'Autocomplete'] as const diff --git a/src/vs/platform/void/electron-main/llmMessageChannel.ts b/src/vs/platform/void/electron-main/llmMessageChannel.ts index f4646f8b..1ca4ece8 100644 --- a/src/vs/platform/void/electron-main/llmMessageChannel.ts +++ b/src/vs/platform/void/electron-main/llmMessageChannel.ts @@ -68,7 +68,7 @@ export class LLMMessageChannel implements IServerChannel { } // the only place sendLLMMessage is actually called - private _callSendLLMMessage(params: ProxyLLMMessageParams) { + private async _callSendLLMMessage(params: ProxyLLMMessageParams) { const { requestId } = params; if (!(requestId in this._abortRefOfRequestId)) @@ -76,9 +76,9 @@ export class LLMMessageChannel implements IServerChannel { 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 }); }, + onText: ({ newText, fullText }) => { console.log('sendLLM: firing onText'); this._onText.fire({ requestId, newText, fullText }); }, + onFinalMessage: ({ fullText }) => { console.log('sendLLM: firing finalMsg'); this._onFinalMessage.fire({ requestId, fullText }); }, + onError: ({ error }) => { console.log('sendLLM: firing err'); this._onError.fire({ requestId, error }); }, abortRef: this._abortRefOfRequestId[requestId], } sendLLMMessage(mainThreadParams, this.metricsService); diff --git a/src/vs/platform/void/electron-main/metricsMainService.ts b/src/vs/platform/void/electron-main/metricsMainService.ts index 42849bef..c27772da 100644 --- a/src/vs/platform/void/electron-main/metricsMainService.ts +++ b/src/vs/platform/void/electron-main/metricsMainService.ts @@ -10,18 +10,12 @@ import { IMetricsService } from '../common/metricsService.js'; import { PostHog } from 'posthog-node' +// posthog-js (old): // posthog.init('phc_UanIdujHiLp55BkUTjB1AuBXcasVkdqRwgnwRlWESH2', { api_host: 'https://us.i.posthog.com', }) // const buildEnv = 'development'; // const buildNumber = '1.0.0'; // const isMac = process.platform === 'darwin'; -// // TODO use commandKey -// const commandKey = isMac ? '⌘' : 'Ctrl'; -// const systemInfo = { -// buildEnv, -// buildNumber, -// isMac, -// } export class MetricsMainService extends Disposable implements IMetricsService { _serviceBrand: undefined; @@ -43,9 +37,9 @@ export class MetricsMainService extends Disposable implements IMetricsService { } capture: IMetricsService['capture'] = (event, params) => { - console.log('Capturing', { event, params }) - console.log('full capture:', { distinctId: this._distinctId, event, properties: params }) - this.client.capture({ distinctId: this._distinctId, event, properties: params }) + const capture = { distinctId: this._distinctId, event, properties: params } as const + // console.log('full capture:', capture) + this.client.capture(capture) } } diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index 70168aa5..0bf2bf99 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -19,7 +19,7 @@ import { IDisposable } from '../../../../../../../base/common/lifecycle.js'; import { ErrorDisplay } from './ErrorDisplay.js'; import { LLMMessageServiceParams } from '../../../../../../../platform/void/common/llmMessageTypes.js'; import { getCmdKey } from '../../../getCmdKey.js' -import { HistoryInputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js'; +import { HistoryInputBox, InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js'; import { VoidInputBox } from './inputs.js'; // read files from VSCode @@ -124,7 +124,7 @@ const ChatBubble = ({ chatMessage }: { chatMessage: ChatMessage }) => { export const SidebarChat = () => { - const chatInputRef = useRef(null) + const inputBoxRef: React.MutableRefObject = useRef(null); const modelService = useService('modelService') @@ -134,11 +134,11 @@ export const SidebarChat = () => { useEffect(() => { const disposables: IDisposable[] = [] disposables.push( - sidebarStateService.onDidFocusChat(() => { chatInputRef.current?.focus() }), - sidebarStateService.onDidBlurChat(() => { chatInputRef.current?.blur() }) + sidebarStateService.onDidFocusChat(() => { inputBoxRef.current?.focus() }), + sidebarStateService.onDidBlurChat(() => { inputBoxRef.current?.blur() }) ) return () => disposables.forEach(d => d.dispose()) - }, [sidebarStateService, chatInputRef]) + }, [sidebarStateService, inputBoxRef]) // config state const voidConfigState = useConfigState() @@ -164,7 +164,7 @@ export const SidebarChat = () => { const onChangeText = useCallback((newStr: string) => { setInstructions(newStr) }, [setInstructions]) const isDisabled = !instructions const formRef = useRef(null) - const inputBoxRef: React.MutableRefObject = useRef(null); + const onSubmit = async (e: FormEvent) => { @@ -298,7 +298,7 @@ export const SidebarChat = () => { diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarProviderSettings.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarProviderSettings.tsx index d82626a0..c4d09f3e 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarProviderSettings.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarProviderSettings.tsx @@ -3,44 +3,45 @@ * Void Editor additions licensed under the AGPLv3 License. *--------------------------------------------------------------------------------------------*/ -import React, { Fragment } from 'react' -import { displayInfoOfSettingName, ProviderName, providerNames } from '../../../../../../../platform/void/common/configTypes.js' +import React, { Fragment, useCallback, useRef } from 'react' +import { displayInfoOfSettingName, ProviderName, providerNames, ProviderSettingName, VoidProviderState } from '../../../../../../../platform/void/common/configTypes.js' import { VoidCheckBox, VoidInputBox, VoidSelectBox } from './inputs.js' import { useConfigState, useService } from '../util/services.js' + +const Setting = ({ val, providerName, settingName }: { val: string, providerName: ProviderName, settingName: any }) => { + + const { title, type, placeholder } = displayInfoOfSettingName(providerName, settingName) + const voidConfigService = useService('configStateService') + + const initValRef = useRef(val) + + return <> +

{title}

+ { { + voidConfigService.setState(providerName, settingName, newVal) + }, [voidConfigService, providerName, settingName]) + } + multiline={false} + />} + + +} + const SettingsForProvider = ({ providerName }: { providerName: ProviderName }) => { const voidConfigState = useConfigState() - const voidConfigService = useService('configStateService') const { models, model, ...others } = voidConfigState[providerName] + const voidConfigService = useService('configStateService') return <>

{providerName}

{/* other settings (e.g. api key) */} - {Object.entries(others).map(([settingName, defaultVal], i) => { - const sName = settingName as keyof typeof others - - const { title, type, placeholder } = displayInfoOfSettingName(providerName, sName) - - return -

{title}

- { - type === 'boolean' ? - { voidConfigService.setState(providerName, sName, newVal ? 'true' : 'false') }} - label={settingName} - checkboxRef={{ current: null }} - /> - : - { () => { voidConfigService.setState(providerName, sName, newVal) } }} - multiline={false} - inputBoxRef={{ current: null }} - />} -
+ {Object.entries(others).map(([settingName, val], i) => { + return })}

{'Models'}

@@ -49,12 +50,10 @@ const SettingsForProvider = ({ providerName }: { providerName: ProviderName }) = : { () => { } }} + onChangeSelection={(newVal) => { voidConfigService.setState(providerName, 'model', newVal) }} selectBoxRef={{ current: null }} />} - - } diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/inputs.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/inputs.tsx index 6c944b5f..82ea209a 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/inputs.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/inputs.tsx @@ -1,29 +1,54 @@ -import React, { useEffect, useRef } from 'react'; +import React, { useCallback, useEffect, useRef } from 'react'; import { useService } from '../util/services.js'; import { HistoryInputBox, InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js'; import { defaultCheckboxStyles, defaultInputBoxStyles, defaultToggleStyles } from '../../../../../../../platform/theme/browser/defaultStyles.js'; import { SelectBox, unthemedSelectBoxStyles } from '../../../../../../../base/browser/ui/selectBox/selectBox.js'; import { Checkbox, Toggle } from '../../../../../../../base/browser/ui/toggle/toggle.js'; - +import { ObjectSettingCheckboxWidget } from '../../../../../preferences/browser/settingsWidgets.js' +import { Widget } from '../../../../../../../base/browser/ui/widget.js'; +import { IDisposable } from '../../../../../../../base/common/lifecycle.js'; // settingitem -export const VoidInputBox = ({ onChangeText, initVal, placeholder, inputBoxRef, multiline }: { + + +export const WidgetComponent = ({ ctor, propsFn, dispose, onCreateInstance } + : { + ctor: { new(...params: CtorParams): Instance }, + propsFn: (container: HTMLDivElement) => CtorParams, + onCreateInstance: (instance: Instance) => IDisposable[], + dispose: (instance: Instance) => void, + } +) => { + const containerRef = useRef(null); + + useEffect(() => { + const instance = new ctor(...propsFn(containerRef.current!)); + const disposables = onCreateInstance(instance); + return () => { + disposables.forEach(d => d.dispose()); + dispose(instance) + } + }, [ctor, propsFn, dispose, onCreateInstance, containerRef]) + + return
+} + + + +export const VoidInputBox = ({ onChangeText, onCreateInstance, initVal, placeholder, multiline }: { onChangeText: (value: string) => void; + onCreateInstance?: { current: InputBox | null } | ((instance: InputBox) => void | IDisposable[]); placeholder: string; - inputBoxRef: React.MutableRefObject; multiline: boolean; initVal: string; }) => { const contextViewProvider = useService('contextViewService'); - const containerRef = useRef(null); - useEffect(() => { - if (!containerRef.current) return; - - // create and mount the HistoryInputBox - inputBoxRef.current = new InputBox( - containerRef.current, + return [ + container, contextViewProvider, { inputBoxStyles: { @@ -34,32 +59,29 @@ export const VoidInputBox = ({ onChangeText, initVal, placeholder, inputBoxRef, flexibleHeight: multiline, flexibleMaxHeight: 500, flexibleWidth: false, - } - ); - inputBoxRef.current.value = initVal; - - - inputBoxRef.current.onDidChange((newStr) => { - console.log('CHANGE TEXT on inputbox', newStr) - onChangeText(newStr) - }) - - // cleanup - return () => { - if (inputBoxRef.current) { - inputBoxRef.current.dispose(); - if (containerRef.current) { - while (containerRef.current.firstChild) { - containerRef.current.removeChild(containerRef.current.firstChild); - } - } - inputBoxRef.current = null; + ] as const, [contextViewProvider, placeholder, multiline])} + dispose={useCallback((instance: InputBox) => { + instance.dispose() + instance.element.remove() + }, [])} + onCreateInstance={useCallback((instance: InputBox) => { + instance.value = initVal + const disposables: IDisposable[] = [] + disposables.push( + instance.onDidChange((newText) => onChangeText(newText)) + ) + if (typeof onCreateInstance === 'function') { + const ds = onCreateInstance(instance) ?? [] + disposables.push(...ds) } - }; - }, [inputBoxRef, contextViewProvider, placeholder, multiline, initVal, onChangeText]); - - return
; + if (typeof onCreateInstance === 'object') { + onCreateInstance.current = instance + } + return disposables + }, [initVal, onChangeText, onCreateInstance]) + } + /> }; @@ -113,27 +135,39 @@ export const VoidSelectBox = ({ onChangeSelection, initVal, selectBoxRef, option export const VoidCheckBox = ({ onChangeChecked, initVal, label, checkboxRef, }: { onChangeChecked: (checked: boolean) => void; initVal: boolean; - checkboxRef: React.MutableRefObject; + checkboxRef: React.MutableRefObject; label: string; }) => { const containerRef = useRef(null); + const themeService = useService('themeService'); + const contextViewService = useService('contextViewService'); + const hoverService = useService('hoverService'); + useEffect(() => { if (!containerRef.current) return; // Create and mount the Checkbox using VSCode's implementation - checkboxRef.current = new Toggle({ - title: label, - isChecked: initVal, - ...defaultToggleStyles - }); - containerRef.current.appendChild(checkboxRef.current.domNode); + checkboxRef.current = new ObjectSettingCheckboxWidget( + containerRef.current, + themeService, + contextViewService, + hoverService, + ); + + + checkboxRef.current.setValue([{ + key: { type: 'string', data: label }, + value: { type: 'boolean', data: initVal }, + removable: false, + resetable: true, + }]) + + checkboxRef.current.onDidChangeList((list) => { + onChangeChecked(!!list); + }) - checkboxRef.current.onChange(checked => { - console.log('CHANGE checked state on checkbox', checked); - onChangeChecked(checked); - }); // cleanup return () => { diff --git a/src/vs/workbench/contrib/void/browser/registerSidebar.ts b/src/vs/workbench/contrib/void/browser/registerSidebar.ts index be81879f..327fa1b6 100644 --- a/src/vs/workbench/contrib/void/browser/registerSidebar.ts +++ b/src/vs/workbench/contrib/void/browser/registerSidebar.ts @@ -70,6 +70,9 @@ export type ReactServicesType = { sendLLMMessageService: ISendLLMMessageService; clipboardService: IClipboardService; + themeService: IThemeService, + hoverService: IHoverService, + contextViewService: IContextViewService; contextMenuService: IContextMenuService; } @@ -113,6 +116,8 @@ class VoidSidebarViewPane extends ViewPane { inlineDiffService: accessor.get(IInlineDiffsService), sendLLMMessageService: accessor.get(ISendLLMMessageService), clipboardService: accessor.get(IClipboardService), + themeService: accessor.get(IThemeService), + hoverService: accessor.get(IHoverService), contextViewService: accessor.get(IContextViewService), contextMenuService: accessor.get(IContextMenuService), }