bug fixes and migrate to custom VoidInputBox2

This commit is contained in:
Andrew Pareles 2025-01-13 02:12:43 -08:00
parent ec2f04be91
commit ae4ece1826
4 changed files with 125 additions and 49 deletions

View file

@ -10,16 +10,15 @@ import { KeybindingWeight } from '../../../../platform/keybinding/common/keybind
import { IMetricsService } from '../../../../platform/void/common/metricsService.js';
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
import { IInlineDiffsService } from './inlineDiffsService.js';
import { InputBox } from '../../../../base/browser/ui/inputbox/inputBox.js';
import { roundRangeToLines } from './sidebarActions.js';
import { VOID_CTRL_K_ACTION_ID } from './actionIDs.js';
export type QuickEditPropsType = {
diffareaid: number,
onGetInputBox: (i: InputBox) => void;
textAreaRef: (ref: HTMLTextAreaElement | null) => void;
onChangeHeight: (height: number) => void;
onUserUpdateText: (text: string) => void;
onChangeText: (text: string) => void;
initText: string | null;
}

View file

@ -17,7 +17,7 @@ import { IDisposable } from '../../../../../../../base/common/lifecycle.js';
import { ErrorDisplay } from './ErrorDisplay.js';
import { OnError, ServiceSendLLMMessageParams } from '../../../../../../../platform/void/common/llmMessageTypes.js';
import { HistoryInputBox, InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js';
import { VoidCodeEditorProps, VoidInputBox } from '../util/inputs.js';
import { TextAreaFns, VoidCodeEditorProps, VoidInputBox2 } from '../util/inputs.js';
import { ModelDropdown } from '../void-settings-tsx/ModelDropdown.js';
import { chat_systemMessage, chat_prompt } from '../../../prompt/prompts.js';
import { ISidebarStateService } from '../../../sidebarStateService.js';
@ -176,7 +176,7 @@ const DEFAULT_BUTTON_SIZE = 22;
export const ButtonSubmit = ({ className, disabled, ...props }: ButtonProps & Required<Pick<ButtonProps, 'disabled'>>) => {
return <button
type='submit'
type='button'
className={`rounded-full flex-shrink-0 flex-grow-0 cursor-pointer flex items-center justify-center
${disabled ? 'bg-vscode-disabled-fg' : 'bg-white'}
${className}
@ -480,7 +480,8 @@ const ChatBubble = ({ chatMessage, isLoading }: {
export const SidebarChat = () => {
const inputBoxRef: React.MutableRefObject<InputBox | null> = useRef(null);
const textAreaRef = useRef<HTMLTextAreaElement | null>(null)
const textAreaFnsRef = useRef<TextAreaFns | null>(null)
const accessor = useAccessor()
const modelService = accessor.get('IModelService')
@ -491,11 +492,11 @@ export const SidebarChat = () => {
useEffect(() => {
const disposables: IDisposable[] = []
disposables.push(
sidebarStateService.onDidFocusChat(() => { inputBoxRef.current?.focus() }),
sidebarStateService.onDidBlurChat(() => { inputBoxRef.current?.blur() })
sidebarStateService.onDidFocusChat(() => { textAreaRef.current?.focus() }),
sidebarStateService.onDidBlurChat(() => { textAreaRef.current?.blur() })
)
return () => disposables.forEach(d => d.dispose())
}, [sidebarStateService, inputBoxRef])
}, [sidebarStateService, textAreaRef])
const { currentTab, isHistoryOpen } = useSidebarState()
@ -516,8 +517,9 @@ export const SidebarChat = () => {
// state of current message
const [instructions, setInstructions] = useState('') // the user's instructions
const isDisabled = !instructions.trim()
const initVal = ''
const [instructionsAreEmpty, setInstructionsAreEmpty] = useState(!initVal)
const isDisabled = instructionsAreEmpty
const [sidebarRef, sidebarDimensions] = useResizeObserver()
const [formRef, formDimensions] = useResizeObserver()
@ -525,14 +527,8 @@ export const SidebarChat = () => {
useScrollbarStyles(sidebarRef)
// const [formHeight, setFormHeight] = useState(0) // TODO should use resize observer instead
// const [sidebarHeight, setSidebarHeight] = useState(0)
const onChangeText = useCallback((newStr: string) => { setInstructions(newStr) }, [setInstructions])
const onSubmit = async () => {
const onSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault()
if (isDisabled) return
if (isLoading) return
@ -561,6 +557,7 @@ export const SidebarChat = () => {
threadsStateService.addMessageToCurrentThread(systemPromptElt)
// add user's message to chat history
const instructions = textAreaRef.current?.value ?? ''
const userHistoryElt: ChatMessage = { role: 'user', content: chat_prompt(instructions, selections), displayContent: instructions, selections: selections }
threadsStateService.addMessageToCurrentThread(userHistoryElt)
@ -569,9 +566,9 @@ export const SidebarChat = () => {
// send message to LLM
setIsLoading(true) // must come before message is sent so onError will work
setLatestError(null)
if (inputBoxRef.current) {
inputBoxRef.current.value = ''; // this triggers onDidChangeText
inputBoxRef.current.blur();
if (textAreaRef.current) {
textAreaFnsRef.current?.setValue('') // triggers onChange
textAreaRef.current.blur();
}
const object: ServiceSendLLMMessageParams = {
@ -609,7 +606,7 @@ export const SidebarChat = () => {
threadsStateService.setStaging([]) // clear staging
inputBoxRef.current?.focus() // focus input after submit
textAreaRef.current?.focus() // focus input after submit
}
@ -682,7 +679,7 @@ export const SidebarChat = () => {
<div // this div is used to position the input box properly
className={`right-0 left-0 m-2 z-[999] overflow-hidden ${previousMessages.length > 0 ? 'absolute bottom-0' : ''}`}
>
<form
<div
ref={formRef}
className={`
flex flex-col gap-2 p-2 relative input text-left shrink-0
@ -692,17 +689,8 @@ export const SidebarChat = () => {
max-h-[80vh] overflow-y-auto
border border-void-border-3 focus-within:border-void-border-1 hover:border-void-border-1
`}
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
onSubmit(e)
}
}}
onSubmit={(e) => {
console.log('submit!')
onSubmit(e)
}}
onClick={(e) => {
inputBoxRef.current?.focus()
textAreaRef.current?.focus()
}}
>
{/* top row */}
@ -746,10 +734,16 @@ export const SidebarChat = () => {
>
{/* text input */}
<VoidInputBox
<VoidInputBox2
placeholder={`${keybindingString} to select`}
onChangeText={onChangeText}
inputBoxRef={inputBoxRef}
onChangeText={useCallback((newStr: string) => { setInstructionsAreEmpty(!newStr) }, [setInstructionsAreEmpty])}
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
onSubmit()
}
}}
ref={textAreaRef}
fnsRef={textAreaFnsRef}
multiline={true}
/>
</div>
@ -777,13 +771,14 @@ export const SidebarChat = () => {
:
// submit button (up arrow)
<ButtonSubmit
onClick={onSubmit}
disabled={isDisabled}
/>
}
</div>
</form>
</div>
</div >
</div >
}

View file

@ -3,7 +3,7 @@
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import React, { useCallback, useEffect, useId, useRef, useState } from 'react';
import React, { forwardRef, MutableRefObject, useCallback, useEffect, useId, useRef, useState } from 'react';
import { IInputBoxStyles, InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js';
import { defaultCheckboxStyles, defaultInputBoxStyles, defaultSelectBoxStyles } from '../../../../../../../platform/theme/browser/defaultStyles.js';
import { SelectBox } from '../../../../../../../base/browser/ui/selectBox/selectBox.js';
@ -46,8 +46,84 @@ export const WidgetComponent = <CtorParams extends any[], Instance>({ ctor, prop
}
export type TextAreaFns = { setValue: (v: string) => void, enable: () => void, disable: () => void }
type InputBox2Props = {
placeholder: string;
multiline: boolean;
fnsRef?: { current: null | TextAreaFns };
onChangeText?: (value: string) => void;
onKeyDown?: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;
}
export const VoidInputBox2 = forwardRef<HTMLTextAreaElement, InputBox2Props>(({ placeholder, multiline, fnsRef, onKeyDown, onChangeText }, ref) => {
export const VoidInputBox = ({ onChangeText, onCreateInstance, inputBoxRef, placeholder, multiline, styles }: {
// mirrors whatever is in ref
const textAreaRef = useRef<HTMLTextAreaElement | null>(null)
const adjustHeight = useCallback(() => {
const r = textAreaRef.current
if (!r) return
r.style.height = 'auto';
const newHeight = Math.min(r.scrollHeight + 1, 500);
r.style.height = `${newHeight}px`;
}, []);
const onChange = useCallback(() => {
const r = textAreaRef.current
if (!r) return
onChangeText?.(r.value)
adjustHeight()
}, [onChangeText, adjustHeight])
const [isEnabled, setEnabled] = useState(true)
return (
<textarea
ref={useCallback((r: HTMLTextAreaElement | null) => {
if (fnsRef)
fnsRef.current = {
setValue: (val) => {
const r = textAreaRef.current
if (!r) return
r.value = val
onChange()
},
enable: () => { setEnabled(true) },
disable: () => { setEnabled(false) },
}
textAreaRef.current = r
if (typeof ref === 'function') ref(r)
else if (ref) ref.current = r
adjustHeight()
}, [fnsRef, onChange, setEnabled, adjustHeight])}
disabled={!isEnabled}
className="w-full resize-none max-h-[500px] overflow-y-auto"
onChange={onChange}
onKeyDown={useCallback((e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === 'Enter') {
// Shift + Enter when multiline = newline
const shouldAddNewline = e.shiftKey && multiline
if (!shouldAddNewline) e.preventDefault(); // prevent newline from being created
}
onKeyDown?.(e)
}, [onKeyDown])}
rows={1}
placeholder={placeholder}
/>
)
})
export const VoidInputBox = ({ onChangeText, onCreateInstance, inputBoxRef, placeholder, multiline }: {
onChangeText: (value: string) => void;
styles?: Partial<IInputBoxStyles>,
onCreateInstance?: (instance: InputBox) => void | IDisposable[];
@ -74,7 +150,6 @@ export const VoidInputBox = ({ onChangeText, onCreateInstance, inputBoxRef, plac
inputForeground: "var(--vscode-foreground)",
// inputBackground: 'transparent',
// inputBorder: 'none',
...styles,
},
placeholder,
tooltip: '',
@ -200,7 +275,7 @@ export const VoidCheckBox = ({ label, value, onClick, className }: { label: stri
export const VoidCustomSelectBox = <T extends any>({
options,
selectedOption,
selectedOption: selectedOption_,
onChangeOption,
getOptionName,
getOptionsEqual,
@ -228,9 +303,16 @@ export const VoidCustomSelectBox = <T extends any>({
const buttonRef = useRef<HTMLButtonElement | null>(null);
const measureRef = useRef<HTMLDivElement | null>(null);
if (!selectedOption) {
selectedOption = options[0];
}
// if the selected option is null, use the 0th option as the selected, and set the option to options[0]
useEffect(() => {
if (!options[0]) return
if (!selectedOption_) {
onChangeOption(options[0]);
}
}, [selectedOption_, options])
const selectedOption = !selectedOption_ ? options[0] : selectedOption_
const updatePosition = useCallback(() => {
if (!buttonRef.current || !containerRef.current || !measureRef.current) return;

View file

@ -7,7 +7,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js'
import { ProviderName, SettingName, displayInfoOfSettingName, providerNames, VoidModelInfo, featureFlagNames, displayInfoOfFeatureFlag, customSettingNamesOfProvider, RefreshableProviderName, refreshableProviderNames, displayInfoOfProviderName, defaultProviderSettings, nonlocalProviderNames, localProviderNames } from '../../../../../../../platform/void/common/voidSettingsTypes.js'
import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js'
import { VoidButton, VoidCheckBox, VoidCustomSelectBox, VoidInputBox, VoidSwitch } from '../util/inputs.js'
import { VoidButton, VoidCheckBox, VoidCustomSelectBox, VoidInputBox, VoidInputBox2, VoidSwitch } from '../util/inputs.js'
import { useAccessor, useIsDark, useRefreshModelListener, useRefreshModelState, useSettingsState } from '../util/services.js'
import { X, RefreshCw, Loader2, Check, MoveRight } from 'lucide-react'
import { useScrollbarStyles } from '../util/useScrollbarStyles.js'
@ -96,7 +96,7 @@ const AddModelMenu = ({ onSubmit }: { onSubmit: () => void }) => {
// const providerNameRef = useRef<ProviderName | null>(null)
const [providerName, setProviderName] = useState<ProviderName | null>(null)
const modelNameRef = useRef<string | null>(null)
const modelNameRef = useRef<HTMLTextAreaElement | null>(null)
const [errorString, setErrorString] = useState('')
@ -124,9 +124,9 @@ const AddModelMenu = ({ onSubmit }: { onSubmit: () => void }) => {
{/* model */}
<div className='max-w-44 w-full border border-void-border-2 bg-void-bg-1 text-void-fg-3 text-root'>
<VoidInputBox
<VoidInputBox2
placeholder='Model Name'
onChangeText={useCallback((modelName) => { modelNameRef.current = modelName }, [])}
ref={modelNameRef}
multiline={false}
/>
</div>
@ -134,7 +134,7 @@ const AddModelMenu = ({ onSubmit }: { onSubmit: () => void }) => {
{/* button */}
<div className='max-w-40'>
<VoidButton onClick={() => {
const modelName = modelNameRef.current
const modelName = modelNameRef.current?.value
if (providerName === null) {
setErrorString('Please select a provider.')