From 703debd312e32d2b72af4eefbb692d5dd9a0e4cc Mon Sep 17 00:00:00 2001 From: Rebecca Alpert Date: Thu, 5 Dec 2024 16:03:20 -0500 Subject: [PATCH] Add sample questions field and a11y improvements Tested with VO and did some cleanup. The panel does not get focused on shifts when using VO - I likely won't have time to fix this, but want to note it as an existing bug. --- src/app/FlyoutForm/ExpandingTextInputs.tsx | 119 +++++++++++++++++++++ src/app/FlyoutForm/FlyoutForm.tsx | 67 +++++++++++- src/app/FlyoutHeader.tsx/FlyoutHeader.tsx | 2 +- src/app/FlyoutList/FlyoutList.tsx | 5 +- src/app/app.css | 32 ++++++ 5 files changed, 216 insertions(+), 9 deletions(-) create mode 100644 src/app/FlyoutForm/ExpandingTextInputs.tsx diff --git a/src/app/FlyoutForm/ExpandingTextInputs.tsx b/src/app/FlyoutForm/ExpandingTextInputs.tsx new file mode 100644 index 0000000..055f9f5 --- /dev/null +++ b/src/app/FlyoutForm/ExpandingTextInputs.tsx @@ -0,0 +1,119 @@ +import * as React from 'react'; +import { Button, TextInput } from '@patternfly/react-core'; +import { CheckIcon, CloseIcon, PencilAltIcon } from '@patternfly/react-icons'; + +interface ExpandingTextInputsProps { + handleInputChange: (newQuestion: string, index?: number) => void; + values: string[]; + /** Unique id for expanding text inputs */ + fieldId: string; + onDeleteValue: (index: number) => void; + isDisabled?: boolean; +} + +export const ExpandingTextInputs: React.FunctionComponent = ({ + values, + handleInputChange, + onDeleteValue, + fieldId, + isDisabled = false, +}: ExpandingTextInputsProps) => { + const [inputValue, setInputValue] = React.useState(''); + const [editingIndex, setEditingIndex] = React.useState(); + const [editingInputValue, setEditingInputValue] = React.useState(''); + const handleClick = () => { + handleInputChange(inputValue); + setInputValue(''); + document.getElementById(`${fieldId}-text-input`)?.focus(); + }; + const onEdit = (index: number) => { + setEditingInputValue(values[index] ?? ''); + setEditingIndex(index); + document + .getElementById(`${fieldId}-edit-text-input-${index}`) + ?.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' }); + document.getElementById(`${fieldId}-edit-text-input-${index}`)?.focus(); + }; + + return ( +
+
+ { + setInputValue(value); + }} + aria-label="Enter example question" + /> + +
+ {values.length > 0 && ( +
+ {values.map((value, index) => { + return ( +
+
+ setEditingInputValue(value)} + tabIndex={editingIndex === index ? 0 : -1} + aria-label={ + editingInputValue === '' ? 'Edit example question' : `Edit example question ${editingInputValue}` + } + /> +
+
+ {value} +
+ + + +
+ ); + })} +
+ )} +
+ ); +}; diff --git a/src/app/FlyoutForm/FlyoutForm.tsx b/src/app/FlyoutForm/FlyoutForm.tsx index 27ef1c7..87a7b8f 100644 --- a/src/app/FlyoutForm/FlyoutForm.tsx +++ b/src/app/FlyoutForm/FlyoutForm.tsx @@ -22,6 +22,7 @@ import { } from '@patternfly/react-core'; import { ExclamationCircleIcon } from '@patternfly/react-icons'; import * as React from 'react'; +import { ExpandingTextInputs } from './ExpandingTextInputs'; interface RetrieverAPIResponse { id: string; @@ -41,6 +42,7 @@ interface FlyoutFormProps { } type validate = 'success' | 'error' | 'default'; +type questionsValidate = 'error' | 'default'; export const FlyoutForm: React.FunctionComponent = ({ header, hideFlyout }: FlyoutFormProps) => { const [isLoading, setIsLoading] = React.useState(true); @@ -57,6 +59,8 @@ export const FlyoutForm: React.FunctionComponent = ({ header, h const [validated, setValidated] = React.useState('default'); const [selectedLLM, setSelectedLLM] = React.useState(); const [prompt, setPrompt] = React.useState(); + const [questions, setQuestions] = React.useState([]); + const [questionsValidated, setQuestionsValidated] = React.useState('default'); const { nextStep, prevStep, setReloadList } = useFlyoutWizard(); const { chatbots } = useAppData(); @@ -171,6 +175,35 @@ export const FlyoutForm: React.FunctionComponent = ({ header, h setPrompt(newPrompt); }; + const handleQuestionsChange = (newQuestion: string, index?: number) => { + const newQuestions = [...questions]; + if (index !== undefined && index !== null) { + newQuestions[index] = newQuestion; + setQuestions(newQuestions); + } else { + newQuestions.push(newQuestion); + if (newQuestions.length > 3) { + setQuestionsValidated('error'); + return; + } + setQuestions(newQuestions); + if (newQuestions.length === 3) { + setQuestionsValidated('error'); + } else { + setQuestionsValidated('default'); + } + } + }; + + const onDeleteQuestion = (index: number) => { + const newQuestions = [...questions]; + newQuestions.splice(index, 1); + if (newQuestions.length < 3) { + setQuestionsValidated('default'); + } + setQuestions(newQuestions); + }; + const onRetrieverToggleClick = () => { setIsRetrieverDropdownOpen(!isRetrieverDropdownOpen); }; @@ -189,6 +222,7 @@ export const FlyoutForm: React.FunctionComponent = ({ header, h llmConnectionId: selectedLLM?.id, retrieverConnectionId: selectedRetriever?.id, userPrompt: prompt, + exampleQuestions: questions, }; try { @@ -241,7 +275,7 @@ export const FlyoutForm: React.FunctionComponent = ({ header, h ) : ( <> -
+
{error ? ( ) : ( @@ -280,8 +314,9 @@ export const FlyoutForm: React.FunctionComponent = ({ header, h {retrieverConnections && retrieverConnections.length > 0 && ( - + setIsRetrieverDropdownOpen(isOpen)} @@ -322,6 +357,7 @@ export const FlyoutForm: React.FunctionComponent = ({ header, h {llms && llms.length > 0 && ( setIsLLMDropdownOpen(isOpen)} @@ -330,7 +366,7 @@ export const FlyoutForm: React.FunctionComponent = ({ header, h onOpenChangeKeys={['Escape']} toggle={(toggleRef: React.Ref) => ( - {selectedLLM ? selectedLLM.description : 'Choose an model'} + {selectedLLM ? selectedLLM.description : 'Choose a model'} )} popperProps={{ appendTo: 'inline' }} @@ -365,7 +401,7 @@ export const FlyoutForm: React.FunctionComponent = ({ header, h