diff --git a/src/vs/platform/void/browser/metricsService.ts b/src/vs/platform/void/browser/metricsService.ts deleted file mode 100644 index 860600d0..00000000 --- a/src/vs/platform/void/browser/metricsService.ts +++ /dev/null @@ -1,32 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Glass Devtools, Inc. All rights reserved. - * Void Editor additions licensed under the AGPL 3.0 License. - *--------------------------------------------------------------------------------------------*/ - -import { ProxyChannel } from '../../../base/parts/ipc/common/ipc.js'; -import { IMainProcessService } from '../../ipc/common/mainProcessService.js'; -import { InstantiationType, registerSingleton } from '../../instantiation/common/extensions.js'; -import { IMetricsService } from '../common/metricsService.js'; - -// BROWSER IMPLEMENTATION, calls channel - -export class MetricsService implements IMetricsService { - - readonly _serviceBrand: undefined; - private readonly metricsService: IMetricsService; - - constructor( - @IMainProcessService mainProcessService: IMainProcessService // (only usable on client side) - ) { - this.metricsService = ProxyChannel.toService(mainProcessService.getChannel('void-channel-metrics')); - } - - // call capture on the channel - capture(...params: Parameters) { - this.metricsService.capture(...params); - } - -} - -registerSingleton(IMetricsService, MetricsService, InstantiationType.Eager); - diff --git a/src/vs/platform/void/browser/void.contribution.ts b/src/vs/platform/void/browser/void.contribution.ts deleted file mode 100644 index b9af616e..00000000 --- a/src/vs/platform/void/browser/void.contribution.ts +++ /dev/null @@ -1,12 +0,0 @@ - -// --- browser --- -// metrics -import '../browser/metricsService.js' - -// --- common --- -// llmMessage -import '../browser/llmMessageService.js' - -// voidConfig -import '../common/voidConfigService.js' - diff --git a/src/vs/platform/void/browser/llmMessageService.ts b/src/vs/platform/void/common/llmMessageService.ts similarity index 93% rename from src/vs/platform/void/browser/llmMessageService.ts rename to src/vs/platform/void/common/llmMessageService.ts index f823a945..967b5a9f 100644 --- a/src/vs/platform/void/browser/llmMessageService.ts +++ b/src/vs/platform/void/common/llmMessageService.ts @@ -3,7 +3,7 @@ * Void Editor additions licensed under the AGPL 3.0 License. *--------------------------------------------------------------------------------------------*/ -import { EventLLMMessageOnTextParams, EventLLMMessageOnErrorParams, EventLLMMessageOnFinalMessageParams, ServiceSendLLMMessageParams, MainLLMMessageParams, MainLLMMessageAbortParams, ServiceOllamaListParams, EventOllamaListOnSuccessParams, EventOllamaListOnErrorParams, MainOllamaListParams } from '../common/llmMessageTypes.js'; +import { EventLLMMessageOnTextParams, EventLLMMessageOnErrorParams, EventLLMMessageOnFinalMessageParams, ServiceSendLLMMessageParams, MainLLMMessageParams, MainLLMMessageAbortParams, ServiceOllamaListParams, EventOllamaListOnSuccessParams, EventOllamaListOnErrorParams, MainOllamaListParams } from './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'; @@ -11,21 +11,19 @@ import { generateUuid } from '../../../base/common/uuid.js'; import { createDecorator } from '../../instantiation/common/instantiation.js'; import { Event } from '../../../base/common/event.js'; import { Disposable } from '../../../base/common/lifecycle.js'; -import { IVoidConfigStateService } from '../common/voidConfigService.js'; +import { IVoidConfigStateService } from './voidConfigService.js'; // import { INotificationService } from '../../notification/common/notification.js'; - -// BROWSER IMPLEMENTATION +// calls channel to implement features export const ILLMMessageService = createDecorator('llmMessageService'); -// defines an interface that node/ creates and browser/ uses export interface ILLMMessageService { readonly _serviceBrand: undefined; sendLLMMessage: (params: ServiceSendLLMMessageParams) => string | null; abort: (requestId: string) => void; + ollamaList: (params: ServiceOllamaListParams) => void; } - export class LLMMessageService extends Disposable implements ILLMMessageService { readonly _serviceBrand: undefined; @@ -52,8 +50,7 @@ export class LLMMessageService extends Disposable implements ILLMMessageService // const service = ProxyChannel.toService(mainProcessService.getChannel('void-channel-sendLLMMessage')); // lets you call it like a service this.channel = this.mainProcessService.getChannel('void-channel-llmMessageService') - // this sets up an IPC channel and takes a few ms, so we set up listeners immediately and add hooks to them instead - + // .listen sets up an IPC channel and takes a few ms, so we set up listeners immediately and add hooks to them instead // llm this._register((this.channel.listen('onText_llm') satisfies Event)(e => { this.onTextHooks_llm[e.requestId]?.(e) @@ -74,6 +71,7 @@ export class LLMMessageService extends Disposable implements ILLMMessageService this._register((this.channel.listen('onError_ollama') satisfies Event)(e => { this.onError_ollama[e.requestId]?.(e) })) + } sendLLMMessage(params: ServiceSendLLMMessageParams) { @@ -114,6 +112,7 @@ export class LLMMessageService extends Disposable implements ILLMMessageService this._onRequestIdDone(requestId) } + ollamaList = (params: ServiceOllamaListParams) => { const { onSuccess, onError, ...proxyParams } = params @@ -132,6 +131,7 @@ export class LLMMessageService extends Disposable implements ILLMMessageService } + _onRequestIdDone(requestId: string) { delete this.onTextHooks_llm[requestId] delete this.onFinalMessageHooks_llm[requestId] @@ -142,5 +142,5 @@ export class LLMMessageService extends Disposable implements ILLMMessageService } } -registerSingleton(ILLMMessageService, LLMMessageService, InstantiationType.Delayed); +registerSingleton(ILLMMessageService, LLMMessageService, InstantiationType.Eager); diff --git a/src/vs/platform/void/common/llmMessageTypes.ts b/src/vs/platform/void/common/llmMessageTypes.ts index 95d613bd..3db5d595 100644 --- a/src/vs/platform/void/common/llmMessageTypes.ts +++ b/src/vs/platform/void/common/llmMessageTypes.ts @@ -106,7 +106,7 @@ interface ModelDetails { quantization_level: string; } -type ModelResponse = { +export type ModelResponse = { name: string; modified_at: Date; size: number; @@ -121,7 +121,7 @@ type ModelResponse = { export type OllamaListParams = { settingsOfProvider: SettingsOfProvider; onSuccess: (param: { models: ModelResponse[] }) => void; - onError: (param: { error: any }) => void; + onError: (param: { error: string }) => void; } export type ServiceOllamaListParams = { diff --git a/src/vs/platform/void/common/metricsService.ts b/src/vs/platform/void/common/metricsService.ts index 45c79c2c..039697a0 100644 --- a/src/vs/platform/void/common/metricsService.ts +++ b/src/vs/platform/void/common/metricsService.ts @@ -4,6 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from '../../instantiation/common/instantiation.js'; +import { ProxyChannel } from '../../../base/parts/ipc/common/ipc.js'; +import { IMainProcessService } from '../../ipc/common/mainProcessService.js'; +import { InstantiationType, registerSingleton } from '../../instantiation/common/extensions.js'; export interface IMetricsService { readonly _serviceBrand: undefined; @@ -13,3 +16,24 @@ export interface IMetricsService { export const IMetricsService = createDecorator('metricsService'); +// implemented by calling channel +export class MetricsService implements IMetricsService { + + readonly _serviceBrand: undefined; + private readonly metricsService: IMetricsService; + + constructor( + @IMainProcessService mainProcessService: IMainProcessService // (only usable on client side) + ) { + this.metricsService = ProxyChannel.toService(mainProcessService.getChannel('void-channel-metrics')); + } + + // call capture on the channel + capture(...params: Parameters) { + this.metricsService.capture(...params); + } + +} + +registerSingleton(IMetricsService, MetricsService, InstantiationType.Eager); + diff --git a/src/vs/platform/void/common/refreshModelService.ts b/src/vs/platform/void/common/refreshModelService.ts new file mode 100644 index 00000000..fe223830 --- /dev/null +++ b/src/vs/platform/void/common/refreshModelService.ts @@ -0,0 +1,96 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Glass Devtools, Inc. All rights reserved. + * Void Editor additions licensed under the AGPL 3.0 License. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from '../../instantiation/common/instantiation.js'; +import { InstantiationType, registerSingleton } from '../../instantiation/common/extensions.js'; +import { IVoidConfigStateService } from './voidConfigService.js'; +import { ILLMMessageService } from './llmMessageService.js'; +import { Emitter, Event } from '../../../base/common/event.js'; +import { Disposable } from '../../../base/common/lifecycle.js'; + + +export type RefreshModelState = 'done' | 'loading' + +export interface IRefreshModelService { + readonly _serviceBrand: undefined; + refreshOllamaModels(): void; + onDidChangeState: Event; + state: RefreshModelState; +} + +export const IRefreshModelService = createDecorator('RefreshModelService'); + +export class RefreshModelService extends Disposable implements IRefreshModelService { + + readonly _serviceBrand: undefined; + + private readonly _onDidChangeState = new Emitter(); + readonly onDidChangeState: Event = this._onDidChangeState.event; // this is primarily for use in react, so react can listen + update on state changes + + constructor( + @IVoidConfigStateService private readonly voidConfigStateService: IVoidConfigStateService, + @ILLMMessageService private readonly llmMessageService: ILLMMessageService, + ) { + super() + + // on mount, refresh ollama models + this.refreshOllamaModels() + + // every time ollama.enabled changes, refresh ollama models + let prevVal: string = this.voidConfigStateService.state.settingsOfProvider.ollama.enabled + this._register( + this.voidConfigStateService.onDidChangeState(() => { + const newVal = this.voidConfigStateService.state.settingsOfProvider.ollama.enabled + if (prevVal !== newVal) + this.refreshOllamaModels() + prevVal = newVal + }) + ) + + } + + state: RefreshModelState = 'done' + + private _timeoutId: NodeJS.Timeout | null = null + private _cancelTimeout = () => { + if (this._timeoutId) { + clearTimeout(this._timeoutId) + this._timeoutId = null + } + } + async refreshOllamaModels() { + // cancel any existing poll + this._cancelTimeout() + + // if ollama is disabled, obivously done + if (this.voidConfigStateService.state.settingsOfProvider.ollama.enabled !== 'true') { + this._setState('done') + return + } + + // start loading models + this._setState('loading') + + this.llmMessageService.ollamaList({ + onSuccess: ({ models }) => { + this.voidConfigStateService.setSettingOfProvider('ollama', 'models', models.map(model => model.name)) + this._setState('done') + }, + onError: ({ error }) => { + // poll + console.log('retrying ollamaList:', error) + this._timeoutId = setTimeout(() => this.refreshOllamaModels(), 5000) + } + }) + } + + private _setState(state: RefreshModelState) { + this.state = state + this._onDidChangeState.fire() + } +} + +registerSingleton(IRefreshModelService, RefreshModelService, InstantiationType.Eager); + diff --git a/src/vs/platform/void/common/void.contribution.ts b/src/vs/platform/void/common/void.contribution.ts new file mode 100644 index 00000000..2e12fb9c --- /dev/null +++ b/src/vs/platform/void/common/void.contribution.ts @@ -0,0 +1,11 @@ +// llmMessage +import './llmMessageService.js' + +// voidConfig +import './voidConfigService.js' + +// refreshModel +import './refreshModelService.js' + +// metrics +import './metricsService.js' diff --git a/src/vs/platform/void/common/voidConfigService.ts b/src/vs/platform/void/common/voidConfigService.ts index 0c5f1291..cec77891 100644 --- a/src/vs/platform/void/common/voidConfigService.ts +++ b/src/vs/platform/void/common/voidConfigService.ts @@ -10,15 +10,15 @@ import { IEncryptionService } from '../../encryption/common/encryptionService.js import { registerSingleton, InstantiationType } from '../../instantiation/common/extensions.js'; import { createDecorator } from '../../instantiation/common/instantiation.js'; import { IStorageService, StorageScope, StorageTarget } from '../../storage/common/storage.js'; -import { defaultVoidProviderState, FeatureName, ProviderName, ModelSelectionOfFeature, SettingsOfProvider } from './voidConfigTypes.js'; +import { defaultVoidProviderState, FeatureName, ProviderName, ModelSelectionOfFeature, SettingsOfProvider, SettingName } from './voidConfigTypes.js'; const STORAGE_KEY = 'void.voidConfigStateII' -type SetSettingOfProviderFn = ( - providerName: K, - option: keyof SettingsOfProvider[K], - newVal: string +type SetSettingOfProviderFn = ( + providerName: ProviderName, + settingName: S, + newVal: SettingsOfProvider[ProviderName][S extends keyof SettingsOfProvider[ProviderName] ? S : never], ) => Promise; type SetModelSelectionOfFeature = ( @@ -52,7 +52,7 @@ const defaultState = () => { export const IVoidConfigStateService = createDecorator('VoidConfigStateService'); -class VoidConfigStateService extends Disposable implements IVoidConfigStateService { +class VoidConfigService extends Disposable implements IVoidConfigStateService { _serviceBrand: undefined; private readonly _onDidChangeState = new Emitter(); @@ -75,7 +75,10 @@ class VoidConfigStateService extends Disposable implements IVoidConfigStateServi this.state = defaultState() // read and update the actual state immediately - this._readVoidConfigState().then(voidConfigState => { this._setState(voidConfigState, 'initialState') }) + this._readVoidConfigState().then(voidConfigState => { + this._setState(voidConfigState) + this._onDidGetInitState.fire() + }) } @@ -95,16 +98,16 @@ class VoidConfigStateService extends Disposable implements IVoidConfigStateServi this._storageService.store(STORAGE_KEY, encryptedVoidConfigStr, StorageScope.APPLICATION, StorageTarget.USER); } - setSettingOfProvider: SetSettingOfProviderFn = async (providerName, option, newVal) => { + setSettingOfProvider: SetSettingOfProviderFn = async (providerName, settingName, newVal) => { const newState: VoidConfigState = { ...this.state, settingsOfProvider: { ...this.state.settingsOfProvider, [providerName]: { ...this.state.settingsOfProvider[providerName], - [option]: newVal, + [settingName]: newVal, } - } + }, } // console.log('NEW STATE I', JSON.stringify(newState, null, 2)) @@ -129,14 +132,11 @@ class VoidConfigStateService extends Disposable implements IVoidConfigStateServi // internal function to update state, should be called every time state changes - private async _setState(voidConfigState: VoidConfigState, type: 'usual' | 'initialState' = 'usual') { + private async _setState(voidConfigState: VoidConfigState) { this.state = voidConfigState - if (type === 'usual') - this._onDidChangeState.fire() - else if (type === 'initialState') - this._onDidGetInitState.fire() + this._onDidChangeState.fire() } } -registerSingleton(IVoidConfigStateService, VoidConfigStateService, InstantiationType.Eager); +registerSingleton(IVoidConfigStateService, VoidConfigService, InstantiationType.Eager); diff --git a/src/vs/platform/void/common/voidConfigTypes.ts b/src/vs/platform/void/common/voidConfigTypes.ts index 3f9ef35d..cdd3e18f 100644 --- a/src/vs/platform/void/common/voidConfigTypes.ts +++ b/src/vs/platform/void/common/voidConfigTypes.ts @@ -42,7 +42,7 @@ export const voidInitModelOptions = { models: defaultOpenAIModels, }, ollama: { - models: [],//getDefaultOllamaModels, + models: [], }, openRouter: { models: [], // any string @@ -164,7 +164,7 @@ export const displayInfoOfSettingName = (providerName: ProviderName, settingName } - +// used when waiting and for a type reference export const defaultVoidProviderState: SettingsOfProvider = { anthropic: { ...voidProviderDefaults.anthropic, diff --git a/src/vs/platform/void/electron-main/llmMessage/ollama.ts b/src/vs/platform/void/electron-main/llmMessage/ollama.ts index 98713670..ba04e117 100644 --- a/src/vs/platform/void/electron-main/llmMessage/ollama.ts +++ b/src/vs/platform/void/electron-main/llmMessage/ollama.ts @@ -4,21 +4,35 @@ *--------------------------------------------------------------------------------------------*/ import { Ollama } from 'ollama'; -import { _InternalOllamaListFnType, _InternalSendLLMMessageFnType } from '../../common/llmMessageTypes.js'; +import { _InternalOllamaListFnType, _InternalSendLLMMessageFnType, ModelResponse } from '../../common/llmMessageTypes.js'; import { parseMaxTokensStr } from './util.js'; -export const ollamaList: _InternalOllamaListFnType = async ({ onSuccess, onError, settingsOfProvider }) => { - const thisConfig = settingsOfProvider.ollama - const ollama = new Ollama({ host: thisConfig.endpoint }) - ollama.list() - .then((response) => { - const { models } = response - onSuccess({ models }) - }) - .catch((error) => { - console.error('getDefaultOllamaModels: error:', error) - onError(error) - }) +export const ollamaList: _InternalOllamaListFnType = async ({ onSuccess: onSuccess_, onError: onError_, settingsOfProvider }) => { + + const onSuccess = ({ models }: { models: ModelResponse[] }) => { + onSuccess_({ models }) + } + + const onError = ({ error }: { error: string }) => { + onError_({ error }) + } + + + try { + const thisConfig = settingsOfProvider.ollama + const ollama = new Ollama({ host: thisConfig.endpoint }) + ollama.list() + .then((response) => { + const { models } = response + onSuccess({ models }) + }) + .catch((error) => { + onError({ error: error + '' }) + }) + } + catch (error) { + onError({ error: error + '' }) + } } diff --git a/src/vs/platform/void/electron-main/llmMessageChannel.ts b/src/vs/platform/void/electron-main/llmMessageChannel.ts index 1ea44a05..803539d9 100644 --- a/src/vs/platform/void/electron-main/llmMessageChannel.ts +++ b/src/vs/platform/void/electron-main/llmMessageChannel.ts @@ -31,8 +31,7 @@ export class LLMMessageChannel implements IServerChannel { // stupidly, channels can't take in @IService constructor( private readonly metricsService: IMetricsService, - ) { - } + ) { } // browser uses this to listen for changes listen(_: unknown, event: string): Event { diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/ModelSelectionSettings.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/ModelSelectionSettings.tsx index 3289de59..0eb34136 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/ModelSelectionSettings.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/ModelSelectionSettings.tsx @@ -3,10 +3,10 @@ * Void Editor additions licensed under the AGPL 3.0 License. *--------------------------------------------------------------------------------------------*/ -import { useCallback, useEffect, useRef } from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' import { FeatureName, featureNames, ProviderName, providerNames } from '../../../../../../../platform/void/common/voidConfigTypes.js' import { dummyModelData } from '../../../../../../../platform/void/common/voidConfigModelDefaults.js' -import { useConfigState, useService } from '../util/services.js' +import { useConfigState, useRefreshModelState, useService } from '../util/services.js' import { VoidSelectBox } from './inputs.js' import { SelectBox } from '../../../../../../../base/browser/ui/selectBox/selectBox.js' @@ -59,8 +59,18 @@ export const ModelSelectionOfFeature = ({ featureName }: { featureName: FeatureN }, [voidConfigService, modelOptions, featureName])} />} - {/*

Settings - {featureName}

*/} - {/* {models.map(([providerName, model], i) =>

{providerName} - {model}

)} */} + +} + +const RefreshModels = () => { + const refreshModelState = useRefreshModelState() + const refreshModelService = useService('refreshModelService') + + return <> + + {refreshModelState === 'loading' ? 'loading...' : '✅'} } @@ -70,6 +80,8 @@ export const ModelSelectionSettings = () => { key={featureName} featureName={featureName} />)} + + } diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/VoidProviderSettings.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/VoidProviderSettings.tsx index 6d50d1fe..7296490a 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/VoidProviderSettings.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/VoidProviderSettings.tsx @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react' -import { titleOfProviderName, displayInfoOfSettingName, ProviderName, providerNames, featureNames } from '../../../../../../../platform/void/common/voidConfigTypes.js' +import { titleOfProviderName, displayInfoOfSettingName, ProviderName, providerNames, featureNames, SettingsOfProvider, SettingName, defaultVoidProviderState } from '../../../../../../../platform/void/common/voidConfigTypes.js' import { VoidInputBox } from './inputs.js' import { useConfigState, useService } from '../util/services.js' import { InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js' import ErrorBoundary from './ErrorBoundary.js' -const Setting = ({ providerName, settingName }: { providerName: ProviderName, settingName: any }) => { +const Setting = ({ providerName, settingName }: { providerName: ProviderName, settingName: SettingName }) => { const { title, type, placeholder } = displayInfoOfSettingName(providerName, settingName) const voidConfigService = useService('configStateService') @@ -22,6 +22,7 @@ const Setting = ({ providerName, settingName }: { providerName: ProviderName, se { + voidConfigService.setSettingOfProvider(providerName, settingName, newVal) // if we just disabeld this provider, we should unselect all models that use it if (settingName === 'enabled' && newVal !== 'true') { @@ -54,10 +55,12 @@ const Setting = ({ providerName, settingName }: { providerName: ProviderName, se const SettingsForProvider = ({ providerName }: { providerName: ProviderName }) => { const voidConfigState = useConfigState() const { models, ...others } = voidConfigState[providerName] + return <>

{titleOfProviderName(providerName)}

{/* settings besides models (e.g. api key) */} - {Object.keys(others).map((settingName, i) => { + {Object.keys(others).map((sName, i) => { + const settingName = sName as keyof typeof others return })} diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/mountFnGenerator.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/mountFnGenerator.tsx index 939d2e26..d6b24f8c 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/util/mountFnGenerator.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/util/mountFnGenerator.tsx @@ -15,8 +15,10 @@ export const mountFnGenerator = (Component: React.FC) => (rootElement: HTMLEleme return } - _registerServices(services) + const disposables = _registerServices(services) const root = ReactDOM.createRoot(rootElement) root.render(); + + return disposables } diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx index 1878a105..975f5be6 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx @@ -7,6 +7,8 @@ import { useState, useEffect } from 'react' import { VoidSidebarState, ReactServicesType } from '../../../registerSidebar.js' import { ThreadsState } from '../../../registerThreads.js' import { SettingsOfProvider } from '../../../../../../../platform/void/common/voidConfigTypes.js' +import { RefreshModelState } from '../../../../../../../platform/void/common/refreshModelService.js' +import { IDisposable } from '../../../../../../../base/common/lifecycle.js' // normally to do this you'd use a useEffect that calls .onDidChangeState(), but useEffect mounts too late and misses initial state changes @@ -17,11 +19,13 @@ let services: ReactServicesType let sidebarState: VoidSidebarState let threadsState: ThreadsState let settingsOfProvider: SettingsOfProvider +let refreshModelState: RefreshModelState // React listens by adding a setState function to these: const sidebarStateListeners: Set<(s: VoidSidebarState) => void> = new Set() const threadsStateListeners: Set<(s: ThreadsState) => void> = new Set() const settingsOfProviderListeners: Set<(s: SettingsOfProvider) => void> = new Set() +const refreshModelStateListeners: Set<(s: RefreshModelState) => void> = new Set() // must call this before you can use any of the hooks below // this should only be called ONCE! this is the only place you don't need to dispose onDidChange. If you use state.onDidChange anywhere else, make sure to dispose it! @@ -30,30 +34,47 @@ let wasCalled = false export const _registerServices = (services_: ReactServicesType) => { + const disposables: IDisposable[] = [] + if (wasCalled) console.error(`⚠️ Void _registerServices was called again! It should only be called once.`) wasCalled = true services = services_ - const { sidebarStateService, configStateService, threadsStateService, } = services + const { sidebarStateService, configStateService, threadsStateService, refreshModelService } = services sidebarState = sidebarStateService.state - sidebarStateService.onDidChangeState(() => { - sidebarState = sidebarStateService.state - sidebarStateListeners.forEach(l => l(sidebarState)) - }) - + disposables.push( + sidebarStateService.onDidChangeState(() => { + sidebarState = sidebarStateService.state + sidebarStateListeners.forEach(l => l(sidebarState)) + }) + ) threadsState = threadsStateService.state - threadsStateService.onDidChangeCurrentThread(() => { - threadsState = threadsStateService.state - threadsStateListeners.forEach(l => l(threadsState)) - }) + disposables.push( + threadsStateService.onDidChangeCurrentThread(() => { + threadsState = threadsStateService.state + threadsStateListeners.forEach(l => l(threadsState)) + }) + ) settingsOfProvider = configStateService.state.settingsOfProvider - configStateService.onDidChangeState(() => { - settingsOfProvider = configStateService.state.settingsOfProvider - settingsOfProviderListeners.forEach(l => l(settingsOfProvider)) - }) + disposables.push( + configStateService.onDidChangeState(() => { + settingsOfProvider = configStateService.state.settingsOfProvider + settingsOfProviderListeners.forEach(l => l(settingsOfProvider)) + }) + ) + + refreshModelState = refreshModelService.state + disposables.push( + refreshModelService.onDidChangeState(() => { + refreshModelState = refreshModelService.state + refreshModelStateListeners.forEach(l => l(refreshModelState)) + }) + ) + + return disposables } @@ -96,3 +117,14 @@ export const useThreadsState = () => { }, [ss]) return s } + + +export const useRefreshModelState = () => { + const [s, ss] = useState(refreshModelState) + useEffect(() => { + ss(refreshModelState) + refreshModelStateListeners.add(ss) + return () => { refreshModelStateListeners.delete(ss) } + }, [ss]) + return s +} diff --git a/src/vs/workbench/contrib/void/browser/registerAutocomplete.ts b/src/vs/workbench/contrib/void/browser/registerAutocomplete.ts index 9262cc93..649b310c 100644 --- a/src/vs/workbench/contrib/void/browser/registerAutocomplete.ts +++ b/src/vs/workbench/contrib/void/browser/registerAutocomplete.ts @@ -12,7 +12,7 @@ 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 { ILLMMessageService } from '../../../../platform/void/browser/llmMessageService.js'; +import { ILLMMessageService } from '../../../../platform/void/common/llmMessageService.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { isCodeEditor } from '../../../../editor/browser/editorBrowser.js'; import { EditorResourceAccessor } from '../../../common/editor.js'; diff --git a/src/vs/workbench/contrib/void/browser/registerInlineDiffs.ts b/src/vs/workbench/contrib/void/browser/registerInlineDiffs.ts index 5286d3df..cb4ae858 100644 --- a/src/vs/workbench/contrib/void/browser/registerInlineDiffs.ts +++ b/src/vs/workbench/contrib/void/browser/registerInlineDiffs.ts @@ -29,7 +29,7 @@ import * as dom from '../../../../base/browser/dom.js'; import { Widget } from '../../../../base/browser/ui/widget.js'; import { URI } from '../../../../base/common/uri.js'; import { LLMFeatureSelection, ServiceSendLLMMessageParams } from '../../../../platform/void/common/llmMessageTypes.js'; -import { ILLMMessageService } from '../../../../platform/void/browser/llmMessageService.js'; +import { ILLMMessageService } from '../../../../platform/void/common/llmMessageService.js'; // gets converted to --vscode-void-greenBG, see void.css diff --git a/src/vs/workbench/contrib/void/browser/registerSidebar.ts b/src/vs/workbench/contrib/void/browser/registerSidebar.ts index afab03f1..ccc37fe0 100644 --- a/src/vs/workbench/contrib/void/browser/registerSidebar.ts +++ b/src/vs/workbench/contrib/void/browser/registerSidebar.ts @@ -25,7 +25,7 @@ import { IViewPaneOptions, ViewPane } from '../../../browser/parts/views/viewPan import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { createDecorator, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; -import { Disposable } from '../../../../base/common/lifecycle.js'; +import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { IThreadHistoryService } from './registerThreads.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; @@ -42,10 +42,11 @@ import { IVoidConfigStateService } from '../../../../platform/void/common/voidCo import { IFileService } from '../../../../platform/files/common/files.js'; import { IInlineDiffsService } from './registerInlineDiffs.js'; import { IModelService } from '../../../../editor/common/services/model.js'; -import { ILLMMessageService } from '../../../../platform/void/browser/llmMessageService.js'; +import { ILLMMessageService } from '../../../../platform/void/common/llmMessageService.js'; import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; +import { IRefreshModelService } from '../../../../platform/void/common/refreshModelService.js'; // compare against search.contribution.ts and debug.contribution.ts, scm.contribution.ts (source control) @@ -64,6 +65,7 @@ export type ReactServicesType = { inlineDiffService: IInlineDiffsService; llmMessageService: ILLMMessageService; clipboardService: IClipboardService; + refreshModelService: IRefreshModelService; themeService: IThemeService, hoverService: IHoverService, @@ -113,10 +115,14 @@ class VoidSidebarViewPane extends ViewPane { clipboardService: accessor.get(IClipboardService), themeService: accessor.get(IThemeService), hoverService: accessor.get(IHoverService), + refreshModelService: accessor.get(IRefreshModelService), contextViewService: accessor.get(IContextViewService), contextMenuService: accessor.get(IContextMenuService), } - mountFn(parent, services); + + // mount react + const disposables: IDisposable[] | undefined = mountFn(parent, services); + disposables?.forEach(d => this._register(d)) }); } diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 36bd6ad4..e1ea851c 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -17,7 +17,7 @@ import './browser/workbench.contribution.js'; //#region --- Void // Void added this: import './contrib/void/browser/void.contribution.js'; -import '../platform/void/browser/void.contribution.js'; +import '../platform/void/common/void.contribution.js'; //#endregion