mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
reactify Widget
This commit is contained in:
parent
8b90d7827d
commit
adb504f2e0
8 changed files with 150 additions and 108 deletions
|
|
@ -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<string, IDisposable[]> = {}
|
||||
|
||||
private readonly onTextEvent: Event<ProxyOnTextPayload>
|
||||
private readonly onFinalMessageEvent: Event<ProxyOnFinalMessagePayload>
|
||||
private readonly onErrorEvent: Event<ProxyOnErrorPayload>
|
||||
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
|
||||
|
||||
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<LLMMessageChannel>(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<ProxyOnTextPayload> = this.channel.listen('onText')
|
||||
this._addDisposable(requestId_,
|
||||
onTextEvent(e => {
|
||||
this.onTextEvent(e => {
|
||||
if (requestId_ !== e.requestId) return;
|
||||
onText(e)
|
||||
})
|
||||
)
|
||||
|
||||
const onFinalMessageEvent: Event<ProxyOnFinalMessagePayload> = 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<ProxyOnErrorPayload> = 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_)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@ export type VoidProviderState = {
|
|||
|
||||
type UnionOfKeys<T> = T extends T ? keyof T : never;
|
||||
|
||||
type ProviderSettingName = UnionOfKeys<VoidProviderState[ProviderName]>
|
||||
export type ProviderSettingName = UnionOfKeys<VoidProviderState[ProviderName]>
|
||||
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<HTMLTextAreaElement | null>(null)
|
||||
const inputBoxRef: React.MutableRefObject<InputBox | null> = 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<HTMLFormElement | null>(null)
|
||||
const inputBoxRef: React.MutableRefObject<HistoryInputBox | null> = useRef(null);
|
||||
|
||||
|
||||
const onSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
||||
|
||||
|
|
@ -298,7 +298,7 @@ export const SidebarChat = () => {
|
|||
<VoidInputBox
|
||||
placeholder={`${getCmdKey()}+L to select`}
|
||||
onChangeText={onChangeText}
|
||||
inputBoxRef={inputBoxRef}
|
||||
onCreateInstance={inputBoxRef}
|
||||
multiline={true}
|
||||
initVal=''
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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 <>
|
||||
<h2>{title}</h2>
|
||||
{<VoidInputBox
|
||||
initVal={initValRef.current}
|
||||
placeholder={placeholder}
|
||||
onChangeText={useCallback((newVal) => {
|
||||
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 <>
|
||||
<h1>{providerName}</h1>
|
||||
|
||||
{/* 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 <Fragment key={i}>
|
||||
<h2>{title}</h2>
|
||||
{
|
||||
type === 'boolean' ?
|
||||
<VoidCheckBox
|
||||
initVal={defaultVal === 'true'}
|
||||
onChangeChecked={(newVal) => { voidConfigService.setState(providerName, sName, newVal ? 'true' : 'false') }}
|
||||
label={settingName}
|
||||
checkboxRef={{ current: null }}
|
||||
/>
|
||||
:
|
||||
<VoidInputBox
|
||||
initVal={defaultVal}
|
||||
placeholder={placeholder}
|
||||
onChangeText={(newVal) => { () => { voidConfigService.setState(providerName, sName, newVal) } }}
|
||||
multiline={false}
|
||||
inputBoxRef={{ current: null }}
|
||||
/>}
|
||||
</Fragment>
|
||||
{Object.entries(others).map(([settingName, val], i) => {
|
||||
return <Setting key={settingName} val={val} providerName={providerName} settingName={settingName} />
|
||||
})}
|
||||
|
||||
<h2>{'Models'}</h2>
|
||||
|
|
@ -49,12 +50,10 @@ const SettingsForProvider = ({ providerName }: { providerName: ProviderName }) =
|
|||
: <VoidSelectBox
|
||||
initVal={models[0]}
|
||||
options={models}
|
||||
onChangeSelection={(newVal) => { () => { } }}
|
||||
onChangeSelection={(newVal) => { voidConfigService.setState(providerName, 'model', newVal) }}
|
||||
selectBoxRef={{ current: null }}
|
||||
/>}
|
||||
|
||||
|
||||
|
||||
</>
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = <CtorParams extends any[], Instance>({ ctor, propsFn, dispose, onCreateInstance }
|
||||
: {
|
||||
ctor: { new(...params: CtorParams): Instance },
|
||||
propsFn: (container: HTMLDivElement) => CtorParams,
|
||||
onCreateInstance: (instance: Instance) => IDisposable[],
|
||||
dispose: (instance: Instance) => void,
|
||||
}
|
||||
) => {
|
||||
const containerRef = useRef<HTMLDivElement | null>(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 <div ref={containerRef} className='w-full' />
|
||||
}
|
||||
|
||||
|
||||
|
||||
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<InputBox | null>;
|
||||
multiline: boolean;
|
||||
initVal: string;
|
||||
}) => {
|
||||
const contextViewProvider = useService('contextViewService');
|
||||
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
// create and mount the HistoryInputBox
|
||||
inputBoxRef.current = new InputBox(
|
||||
containerRef.current,
|
||||
return <WidgetComponent
|
||||
ctor={InputBox}
|
||||
propsFn={useCallback((container) => [
|
||||
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 <div ref={containerRef} className="w-full" />;
|
||||
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<Toggle | null>;
|
||||
checkboxRef: React.MutableRefObject<ObjectSettingCheckboxWidget | null>;
|
||||
label: string;
|
||||
}) => {
|
||||
const containerRef = useRef<HTMLDivElement>(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 () => {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue