From cd983f0dd945467dcf1d13f33190d79e866d1e83 Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Tue, 4 Feb 2025 01:20:33 -0800 Subject: [PATCH] refactor input box with no noticable errors --- remote/package-lock.json | 7 + .../src/quick-edit-tsx/QuickEditChat.tsx | 139 +++-------- .../react/src/sidebar-tsx/ErrorDisplay.tsx | 4 +- .../react/src/sidebar-tsx/SidebarChat.tsx | 219 ++++++++++++------ 4 files changed, 188 insertions(+), 181 deletions(-) diff --git a/remote/package-lock.json b/remote/package-lock.json index 0fa0d8e4..0d0df312 100644 --- a/remote/package-lock.json +++ b/remote/package-lock.json @@ -29,6 +29,7 @@ "@xterm/headless": "^5.6.0-beta.64", "@xterm/xterm": "^5.6.0-beta.64", "cookie": "^0.4.0", + "debounced": "1.0.2", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "jschardet": "3.1.3", @@ -396,6 +397,12 @@ "node": ">= 0.6" } }, + "node_modules/debounced": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/debounced/-/debounced-1.0.2.tgz", + "integrity": "sha512-6GPv+l/OOtdb1DKNY70k5ubuJhVjtBjUnujC5vQAHHrMuvBpDXsTc91xEMTdeA3/v4swYHamtdB9XIN7DcKxpw==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", diff --git a/src/vs/workbench/contrib/void/browser/react/src/quick-edit-tsx/QuickEditChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/quick-edit-tsx/QuickEditChat.tsx index da2a0d70..f1a3456a 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/quick-edit-tsx/QuickEditChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/quick-edit-tsx/QuickEditChat.tsx @@ -7,7 +7,7 @@ import React, { FormEvent, useCallback, useEffect, useRef, useState } from 'reac import { useSettingsState, useSidebarState, useChatThreadsState, useQuickEditState, useAccessor } from '../util/services.js'; import { TextAreaFns, VoidInputBox2 } from '../util/inputs.js'; import { QuickEditPropsType } from '../../../quickEditActions.js'; -import { ButtonStop, ButtonSubmit, IconX } from '../sidebar-tsx/SidebarChat.js'; +import { ButtonStop, ButtonSubmit, IconX, VoidInputForm } from '../sidebar-tsx/SidebarChat.js'; import { ModelDropdown } from '../void-settings-tsx/ModelDropdown.js'; import { VOID_CTRL_K_ACTION_ID } from '../../../actionIDs.js'; import { useRefState } from '../util/helpers.js'; @@ -49,12 +49,11 @@ export const QuickEditChat = ({ const [currStreamingDiffZoneRef, setCurrentlyStreamingDiffZone] = useRefState(initStreamingDiffZoneId) const isStreaming = currStreamingDiffZoneRef.current !== null - const onSubmit = useCallback((e: FormEvent) => { + const onSubmit = useCallback(() => { if (isDisabled) return if (currStreamingDiffZoneRef.current !== null) return textAreaFnsRef.current?.disable() - const instructions = textAreaRef.current?.value ?? '' const id = inlineDiffsService.startApplying({ featureName: 'Ctrl+K', diffareaid: diffareaid, @@ -80,109 +79,41 @@ export const QuickEditChat = ({ const keybindingString = accessor.get('IKeybindingService').lookupKeybinding(VOID_CTRL_K_ACTION_ID)?.getLabel() return
-
{ - textAreaRef.current?.focus() - }} + - - {/* // this div is used to position the input box properly */} -
-
- - {/* input */} -
- {/* text input */} - { - textAreaRef.current = r - textAreaRef_(r) - - // if presses the esc key, X - r?.addEventListener('keydown', (e) => { - if (e.key === 'Escape') - onX() - }) - - }, [textAreaRef_, onX])} - - fnsRef={textAreaFnsRef} - - placeholder={`Enter instructions...`} - // ${keybindingString} to select. - - onChangeText={useCallback((newStr: string) => { - setInstructionsAreEmpty(!newStr) - onChangeText_(newStr) - }, [onChangeText_])} - - onKeyDown={(e) => { - if (e.key === 'Enter' && !e.shiftKey) { - onSubmit(e) - return - } - }} - - multiline={true} - /> -
- - {/* X button */} -
- -
-
- - - {/* bottom row */} -
- {/* submit options */} -
- -
- - {/* submit / stop button */} - {isStreaming ? - // stop button - - : - // submit button (up arrow) - + { + textAreaRef.current = r + textAreaRef_(r) + r?.addEventListener('keydown', (e) => { + if (e.key === 'Escape') + onX() + }) + }, [textAreaRef_, onX])} + fnsRef={textAreaFnsRef} + placeholder="Enter instructions..." + onChangeText={useCallback((newStr: string) => { + setInstructionsAreEmpty(!newStr) + onChangeText_(newStr) + }, [onChangeText_])} + onKeyDown={(e) => { + if (e.key === 'Enter' && !e.shiftKey) { + onSubmit() + return } -
-
- - - + }} + multiline={true} + /> +
diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/ErrorDisplay.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/ErrorDisplay.tsx index 689d55c1..8e2d089c 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/ErrorDisplay.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/ErrorDisplay.tsx @@ -6,6 +6,7 @@ import React, { useEffect, useState } from 'react'; import { AlertCircle, ChevronDown, ChevronUp, X } from 'lucide-react'; import { errorDetails } from '../../../../../../../platform/void/common/llmMessageTypes.js'; +import { useSettingsState } from '../util/services.js'; export const ErrorDisplay = ({ @@ -23,8 +24,7 @@ export const ErrorDisplay = ({ const details = errorDetails(fullError) - const message = message_ === 'TypeError: fetch failed' ? 'TypeError: fetch failed. This likely means you specified the wrong endpoint in Void Settings.' : message_ + '' - + const message = message_ === 'TypeError: fetch failed' ? `TypeError for : fetch failed. This likely means you specified the wrong endpoint in Void Settings, or your local model provider like Ollama is powered off.` : message_ + '' return (
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 1854d6cf..7b46422a 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 @@ -3,10 +3,10 @@ * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. *--------------------------------------------------------------------------------------*/ -import React, { ButtonHTMLAttributes, FormEvent, FormHTMLAttributes, Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import React, { ButtonHTMLAttributes, FormEvent, FormHTMLAttributes, Fragment, KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { useAccessor, useSidebarState, useChatThreadsState, useChatThreadsStreamState, useUriState } from '../util/services.js'; +import { useAccessor, useSidebarState, useChatThreadsState, useChatThreadsStreamState, useUriState, useSettingsState } from '../util/services.js'; import { ChatMessage, StagingSelectionItem } from '../../../chatThreadService.js'; import { BlockCode } from '../markdown/BlockCode.js'; @@ -22,6 +22,7 @@ import { VOID_CTRL_L_ACTION_ID } from '../../../actionIDs.js'; import { filenameToVscodeLanguage } from '../../../helpers/detectLanguage.js'; import { VOID_OPEN_SETTINGS_ACTION_ID } from '../../../voidSettingsPane.js'; import { Pencil } from 'lucide-react'; +import { FeatureName } from '../../../../../../../platform/void/common/voidSettingsTypes.js'; export const IconX = ({ size, className = '', ...props }: { size: number, className?: string } & React.SVGProps) => { @@ -133,6 +134,103 @@ export const IconLoading = ({ className = '' }: { className?: string }) => { } + +interface VoidInputFormProps { + // Required + children: React.ReactNode; // This will be the input component + + // Form controls + onSubmit: () => void; + onAbort: () => void; + isStreaming: boolean; + isDisabled?: boolean; + formRef?: React.RefObject; + + // UI customization + featureName: FeatureName; + className?: string; + showModelDropdown?: boolean; + showSelections?: boolean; + selections?: any[]; + onSelectionsChange?: (selections: any[]) => void; + + // Optional close button + onClose?: () => void; +} + +export const VoidInputForm: React.FC = ({ + children, + onSubmit, + onAbort, + onClose, + formRef, + isStreaming = false, + isDisabled = false, + className = '', + showModelDropdown = true, + featureName, + showSelections = false, + selections = [], + onSelectionsChange, +}) => { + return ( +
+ {/* Selections section */} + {showSelections && onSelectionsChange && ( + + )} + + {/* Input section */} +
+ {children} + + {/* Close button (X) if onClose is provided */} + {onClose && ( +
+ +
+ )} +
+ + {/* Bottom row */} +
+ {showModelDropdown && ( +
+ +
+ )} + + {isStreaming ? ( + + ) : ( + + )} +
+ + ); +}; + const useResizeObserver = () => { const ref = useRef(null); const [dimensions, setDimensions] = useState({ height: 0, width: 0 }); @@ -565,20 +663,31 @@ export const SidebarChat = () => { useScrollbarStyles(sidebarRef) - const onSubmit = async () => { + const onSubmit = useCallback(async () => { + + console.log('onSubmit') if (isDisabled) return if (isStreaming) return + console.log('chatThreadsService', chatThreadsService ? chatThreadsService : '!undefined') + // send message to LLM const userMessage = textAreaRef.current?.value ?? '' + console.log('userMessage', userMessage) + console.log('streaming...',) await chatThreadsService.addUserMessageAndStreamResponse(userMessage) + console.log('done streaming',) chatThreadsService.setStaging([]) // clear staging + console.log('set staging',) textAreaFnsRef.current?.setValue('') + console.log('set value',) textAreaRef.current?.focus() // focus input after submit + console.log('textAreaRef', textAreaRef.current) + console.log('focus',) - } + }, [chatThreadsService, isDisabled, isStreaming, textAreaRef, textAreaFnsRef]) const onAbort = () => { const threadId = currentThread.id @@ -611,6 +720,7 @@ export const SidebarChat = () => {
+ const messagesHTML = { - const inputBox =
0 ? 'absolute bottom-0' : ''}`} - > -
{ - textAreaRef.current?.focus() - }} + const onChangeText = useCallback((newStr: string) => { + setInstructionsAreEmpty(!newStr) + }, [setInstructionsAreEmpty]) + const onKeyDown = useCallback((e: KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + onSubmit() + } + }, [onSubmit]) + const inputForm =
0 ? 'absolute bottom-0' : ''}`}> + - {/* top row */} - <> - {/* selections */} - - - - {/* middle row */} -
- - {/* text input */} - { setInstructionsAreEmpty(!newStr) }, [setInstructionsAreEmpty])} - onKeyDown={(e) => { - if (e.key === 'Enter' && !e.shiftKey) { - onSubmit() - } - }} - ref={textAreaRef} - fnsRef={textAreaFnsRef} - multiline={true} - /> -
- - {/* bottom row */} -
- {/* submit options */} -
- -
- - {/* submit / stop button */} - {isStreaming ? - // stop button - - : - // submit button (up arrow) - - } -
-
+ +
return
@@ -724,7 +793,7 @@ export const SidebarChat = () => { {messagesHTML} - {inputBox} + {inputForm}
}