mirror of
https://github.com/voideditor/void
synced 2026-05-23 09:28:23 +00:00
bug fixes and migrate to custom VoidInputBox2
This commit is contained in:
parent
ec2f04be91
commit
ae4ece1826
4 changed files with 125 additions and 49 deletions
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 >
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.')
|
||||
|
|
|
|||
Loading…
Reference in a new issue