Skip to content

Commit c873b49

Browse files
Add clickable help icons for developer settings (#1522)
* initial tooltip * remove extra text * add chat tooltips * improve for clarity * add gpt4 vision tooltip * add ask tooltips * Use help callouts instead * Use help callouts instead * Playwright tests * Updated text * Fix E2E tests * Fix vector settings default * Update e2e test * Adding e2e fix --------- Co-authored-by: John Aziz <[email protected]>
1 parent e8874a5 commit c873b49

File tree

8 files changed

+534
-158
lines changed

8 files changed

+534
-158
lines changed

app/frontend/src/components/GPT4VSettings/GPT4VSettings.tsx

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import { useEffect, useState } from "react";
2-
import { Stack, Checkbox, IDropdownOption, Dropdown } from "@fluentui/react";
2+
import { Stack, Checkbox, ICheckboxProps, IDropdownOption, IDropdownProps, Dropdown } from "@fluentui/react";
3+
import { useId } from "@fluentui/react-hooks";
34

45
import styles from "./GPT4VSettings.module.css";
56
import { GPT4VInput } from "../../api";
7+
import { HelpCallout } from "../../components/HelpCallout";
8+
import { toolTipText } from "../../i18n/tooltips.js";
69

710
interface Props {
811
gpt4vInputs: GPT4VInput;
@@ -32,23 +35,42 @@ export const GPT4VSettings = ({ updateGPT4VInputs, updateUseGPT4V, isUseGPT4V, g
3235
useGPT4V && updateGPT4VInputs(GPT4VInput.TextAndImages);
3336
}, [useGPT4V]);
3437

38+
const useGPT4VId = useId("useGPT4V");
39+
const useGPT4VFieldId = useId("useGPT4VField");
40+
const gpt4VInputId = useId("gpt4VInput");
41+
const gpt4VInputFieldId = useId("gpt4VInputField");
42+
3543
return (
3644
<Stack className={styles.container} tokens={{ childrenGap: 10 }}>
37-
<Checkbox checked={useGPT4V} label="Use GPT vision model" onChange={onuseGPT4V} />
45+
<Checkbox
46+
id={useGPT4VFieldId}
47+
checked={useGPT4V}
48+
label="Use GPT vision model"
49+
onChange={onuseGPT4V}
50+
aria-labelledby={useGPT4VId}
51+
onRenderLabel={(props: ICheckboxProps | undefined) => (
52+
<HelpCallout labelId={useGPT4VId} fieldId={useGPT4VFieldId} helpText={toolTipText.useGPT4Vision} label={props?.label} />
53+
)}
54+
/>
3855
{useGPT4V && (
3956
<Dropdown
57+
id={gpt4VInputFieldId}
4058
selectedKey={vectorFieldOption}
41-
label="GPT-4 Turbo with Vision Inputs"
59+
label="GPT vision model inputs"
4260
options={[
4361
{
4462
key: GPT4VInput.TextAndImages,
45-
text: "Images and text from index"
63+
text: "Images and text"
4664
},
47-
{ text: "Images only", key: GPT4VInput.Images },
48-
{ text: "Text only", key: GPT4VInput.Texts }
65+
{ text: "Images", key: GPT4VInput.Images },
66+
{ text: "Text", key: GPT4VInput.Texts }
4967
]}
5068
required
5169
onChange={onSetGPT4VInput}
70+
aria-labelledby={gpt4VInputId}
71+
onRenderLabel={(props: IDropdownProps | undefined) => (
72+
<HelpCallout labelId={gpt4VInputId} fieldId={gpt4VInputFieldId} helpText={toolTipText.gpt4VisionInputs} label={props?.label} />
73+
)}
5274
/>
5375
)}
5476
</Stack>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { ITextFieldProps, DefaultButton, IconButton, IButtonStyles, Callout, IStackTokens, Stack, IStackStyles, initializeIcons } from "@fluentui/react";
2+
import { useBoolean, useId } from "@fluentui/react-hooks";
3+
4+
const stackTokens: IStackTokens = {
5+
childrenGap: 4,
6+
maxWidth: 300
7+
};
8+
9+
const labelCalloutStackStyles: Partial<IStackStyles> = { root: { padding: 20 } };
10+
const iconButtonStyles: Partial<IButtonStyles> = { root: { marginBottom: -3 } };
11+
const iconProps = { iconName: "Info" };
12+
13+
interface IHelpCalloutProps {
14+
label: string | undefined;
15+
labelId: string;
16+
fieldId: string | undefined;
17+
helpText: string;
18+
}
19+
20+
export const HelpCallout = (props: IHelpCalloutProps): JSX.Element => {
21+
const [isCalloutVisible, { toggle: toggleIsCalloutVisible }] = useBoolean(false);
22+
const descriptionId: string = useId("description");
23+
const iconButtonId: string = useId("iconButton");
24+
25+
return (
26+
<>
27+
<Stack horizontal verticalAlign="center" tokens={stackTokens}>
28+
<label id={props.labelId} htmlFor={props.fieldId}>
29+
{props.label}
30+
</label>
31+
<IconButton id={iconButtonId} iconProps={iconProps} title="Info" ariaLabel="Info" onClick={toggleIsCalloutVisible} styles={iconButtonStyles} />
32+
</Stack>
33+
{isCalloutVisible && (
34+
<Callout target={"#" + iconButtonId} setInitialFocus onDismiss={toggleIsCalloutVisible} ariaDescribedBy={descriptionId} role="alertdialog">
35+
<Stack tokens={stackTokens} horizontalAlign="start" styles={labelCalloutStackStyles}>
36+
<span id={descriptionId}>{props.helpText}</span>
37+
<DefaultButton onClick={toggleIsCalloutVisible}>Close</DefaultButton>
38+
</Stack>
39+
</Callout>
40+
)}
41+
</>
42+
);
43+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./HelpCallout";

app/frontend/src/components/VectorSettings/VectorSettings.tsx

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import { useEffect, useState } from "react";
2-
import { ChoiceGroup, IChoiceGroupOption, Stack, IDropdownOption, Dropdown } from "@fluentui/react";
2+
import { Stack, IDropdownOption, Dropdown, IDropdownProps } from "@fluentui/react";
3+
import { useId } from "@fluentui/react-hooks";
34

45
import styles from "./VectorSettings.module.css";
6+
import { HelpCallout } from "../../components/HelpCallout";
57
import { RetrievalMode, VectorFieldOptions } from "../../api";
8+
import { toolTipText } from "../../i18n/tooltips.js";
69

710
interface Props {
811
showImageOptions?: boolean;
@@ -11,39 +14,18 @@ interface Props {
1114
updateVectorFields: (options: VectorFieldOptions[]) => void;
1215
}
1316

14-
const vectorFields: IChoiceGroupOption[] = [
15-
{
16-
key: VectorFieldOptions.Embedding,
17-
text: "Text Embeddings"
18-
},
19-
{
20-
key: VectorFieldOptions.ImageEmbedding,
21-
text: "Image Embeddings"
22-
},
23-
{
24-
key: VectorFieldOptions.Both,
25-
text: "Text and Image embeddings"
26-
}
27-
];
28-
2917
export const VectorSettings = ({ updateRetrievalMode, updateVectorFields, showImageOptions, defaultRetrievalMode }: Props) => {
3018
const [retrievalMode, setRetrievalMode] = useState<RetrievalMode>(RetrievalMode.Hybrid);
31-
const [vectorFieldOption, setVectorFieldOption] = useState<string>();
19+
const [vectorFieldOption, setVectorFieldOption] = useState<VectorFieldOptions>(VectorFieldOptions.Both);
3220

3321
const onRetrievalModeChange = (_ev: React.FormEvent<HTMLDivElement>, option?: IDropdownOption<RetrievalMode> | undefined) => {
3422
setRetrievalMode(option?.data || RetrievalMode.Hybrid);
3523
updateRetrievalMode(option?.data || RetrievalMode.Hybrid);
3624
};
3725

38-
const onVectorFieldsChange = (_ev?: React.FormEvent<HTMLElement | HTMLInputElement>, option?: IChoiceGroupOption) => {
39-
option && setVectorFieldOption(option.key);
40-
let list;
41-
if (option?.key === "both") {
42-
list = [VectorFieldOptions.Embedding, VectorFieldOptions.ImageEmbedding];
43-
} else {
44-
list = [option?.key as VectorFieldOptions];
45-
}
46-
updateVectorFields(list);
26+
const onVectorFieldsChange = (_ev: React.FormEvent<HTMLDivElement>, option?: IDropdownOption<RetrievalMode> | undefined) => {
27+
setVectorFieldOption(option?.key as VectorFieldOptions);
28+
updateVectorFields([option?.key as VectorFieldOptions]);
4729
};
4830

4931
useEffect(() => {
@@ -52,9 +34,15 @@ export const VectorSettings = ({ updateRetrievalMode, updateVectorFields, showIm
5234
: updateVectorFields([VectorFieldOptions.Embedding]);
5335
}, [showImageOptions]);
5436

37+
const retrievalModeId = useId("retrievalMode");
38+
const retrievalModeFieldId = useId("retrievalModeField");
39+
const vectorFieldsId = useId("vectorFields");
40+
const vectorFieldsFieldId = useId("vectorFieldsField");
41+
5542
return (
5643
<Stack className={styles.container} tokens={{ childrenGap: 10 }}>
5744
<Dropdown
45+
id={retrievalModeFieldId}
5846
label="Retrieval mode"
5947
selectedKey={defaultRetrievalMode.toString()}
6048
options={[
@@ -64,15 +52,26 @@ export const VectorSettings = ({ updateRetrievalMode, updateVectorFields, showIm
6452
]}
6553
required
6654
onChange={onRetrievalModeChange}
55+
aria-labelledby={retrievalModeId}
56+
onRenderLabel={(props: IDropdownProps | undefined) => (
57+
<HelpCallout labelId={retrievalModeId} fieldId={retrievalModeFieldId} helpText={toolTipText.retrievalMode} label={props?.label} />
58+
)}
6759
/>
6860

6961
{showImageOptions && [RetrievalMode.Vectors, RetrievalMode.Hybrid].includes(retrievalMode) && (
70-
<ChoiceGroup
71-
options={vectorFields}
62+
<Dropdown
63+
id={vectorFieldsFieldId}
64+
label="Vector fields (Multi-query vector search)"
65+
options={[
66+
{ key: VectorFieldOptions.Embedding, text: "Text Embeddings", selected: vectorFieldOption === VectorFieldOptions.Embedding },
67+
{ key: VectorFieldOptions.ImageEmbedding, text: "Image Embeddings", selected: vectorFieldOption === VectorFieldOptions.ImageEmbedding },
68+
{ key: VectorFieldOptions.Both, text: "Text and Image embeddings", selected: vectorFieldOption === VectorFieldOptions.Both }
69+
]}
7270
onChange={onVectorFieldsChange}
73-
selectedKey={vectorFieldOption}
74-
defaultSelectedKey={VectorFieldOptions.Both}
75-
label="Vector Fields (Multi-query vector search)"
71+
aria-labelledby={vectorFieldsId}
72+
onRenderLabel={(props: IDropdownProps | undefined) => (
73+
<HelpCallout labelId={vectorFieldsId} fieldId={vectorFieldsFieldId} helpText={toolTipText.vectorFields} label={props?.label} />
74+
)}
7675
/>
7776
)}
7877
</Stack>

app/frontend/src/i18n/tooltips.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Keep values less than 20 words.
2+
// Don't add links to the tooltips.
3+
export const toolTipText = {
4+
promptTemplate:
5+
"Overrides the prompt used to generate the answer based on the question and search results. To append to existing prompt instead of replace whole prompt, start your prompt with '>>>'.",
6+
temperature:
7+
"Sets the temperature of the request to the LLM that generates the answer. Higher temperatures result in more creative responses, but they may be less grounded.",
8+
searchScore:
9+
"Sets a minimum score for search results coming back from Azure AI search. The score range depends on whether you're using hybrid (default), vectors only, or text only.",
10+
rerankerScore:
11+
"Sets a minimum score for search results coming back from the semantic reranker. The score always ranges between 1-4. The higher the score, the more semantically relevant the result is to the question.",
12+
retrieveNumber:
13+
"Sets the number of search results to retrieve from Azure AI search. More results may increase the likelihood of finding the correct answer, but may lead to the model getting 'lost in the middle'.",
14+
excludeCategory: "Specifies a category to exclude from the search results. There are no categories used in the default data set.",
15+
useSemanticReranker: "Enables the Azure AI Search semantic ranker, a model that re-ranks search results based on semantic similarity to the user's query.",
16+
useSemanticCaptions:
17+
"Sends semantic captions to the LLM instead of the full search result. A semantic caption is extracted from a search result during the process of semantic ranking.",
18+
suggestFollowupQuestions: "Asks the LLM to suggest follow-up questions based on the user's query.",
19+
useGPT4Vision: "Uses GPT-4-Turbo with Vision to generate responses based on images and text from the index.",
20+
vectorFields:
21+
"Specifies which embedding fields in the Azure AI Search Index will be searched, both the 'Images and text' embeddings, 'Images' only, or 'Text' only.",
22+
gpt4VisionInputs:
23+
"Sets what will be send to the vision model. 'Images and text' sends both images and text to the model, 'Images' sends only images, and 'Text' sends only text.",
24+
retrievalMode:
25+
"Sets the retrieval mode for the Azure AI Search query. `Vectors + Text (Hybrid)` uses a combination of vector search and full text search, `Vectors` uses only vector search, and `Text` uses only full text search. Hybrid is generally optimal.",
26+
streamChat: "Continuously streams the response to the chat UI as it is generated.",
27+
useOidSecurityFilter: "Filter search results based on the authenticated user's OID.",
28+
useGroupsSecurityFilter: "Filter search results based on the authenticated user's groups."
29+
};

0 commit comments

Comments
 (0)