Skip to content

Commit 86b6dad

Browse files
bnodirpamelafox
andauthored
Refactor settings into a single component across Chat/Ask (#2111)
* Refactor settings code across Chat/Ask * Refactor settings code across Chat/Ask * Update docs for settings change * Update playwright test --------- Co-authored-by: Pamela Fox <[email protected]>
1 parent fa9a638 commit 86b6dad

File tree

6 files changed

+508
-669
lines changed

6 files changed

+508
-669
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.settingsSeparator {
2+
margin-top: 0.75rem;
3+
}
Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
import { useId } from "@fluentui/react-hooks";
2+
import { useTranslation } from "react-i18next";
3+
import { TextField, ITextFieldProps, Checkbox, ICheckboxProps, Dropdown, IDropdownProps, IDropdownOption } from "@fluentui/react";
4+
import { HelpCallout } from "../HelpCallout";
5+
import { GPT4VSettings } from "../GPT4VSettings";
6+
import { VectorSettings } from "../VectorSettings";
7+
import { RetrievalMode, VectorFieldOptions, GPT4VInput } from "../../api";
8+
import styles from "./Settings.module.css";
9+
10+
// Add type for onRenderLabel
11+
type RenderLabelType = ITextFieldProps | IDropdownProps | ICheckboxProps;
12+
13+
export interface SettingsProps {
14+
promptTemplate: string;
15+
temperature: number;
16+
retrieveCount: number;
17+
seed: number | null;
18+
minimumSearchScore: number;
19+
minimumRerankerScore: number;
20+
useSemanticRanker: boolean;
21+
useSemanticCaptions: boolean;
22+
excludeCategory: string;
23+
includeCategory: string;
24+
retrievalMode: RetrievalMode;
25+
useGPT4V: boolean;
26+
gpt4vInput: GPT4VInput;
27+
vectorFieldList: VectorFieldOptions[];
28+
showSemanticRankerOption: boolean;
29+
showGPT4VOptions: boolean;
30+
showVectorOption: boolean;
31+
useOidSecurityFilter: boolean;
32+
useGroupsSecurityFilter: boolean;
33+
useLogin: boolean;
34+
loggedIn: boolean;
35+
requireAccessControl: boolean;
36+
className?: string;
37+
onChange: (field: string, value: any) => void;
38+
shouldStream?: boolean; // Only used in Chat
39+
useSuggestFollowupQuestions?: boolean; // Only used in Chat
40+
promptTemplatePrefix?: string;
41+
promptTemplateSuffix?: string;
42+
showSuggestFollowupQuestions?: boolean;
43+
}
44+
45+
export const Settings = ({
46+
promptTemplate,
47+
temperature,
48+
retrieveCount,
49+
seed,
50+
minimumSearchScore,
51+
minimumRerankerScore,
52+
useSemanticRanker,
53+
useSemanticCaptions,
54+
excludeCategory,
55+
includeCategory,
56+
retrievalMode,
57+
useGPT4V,
58+
gpt4vInput,
59+
vectorFieldList,
60+
showSemanticRankerOption,
61+
showGPT4VOptions,
62+
showVectorOption,
63+
useOidSecurityFilter,
64+
useGroupsSecurityFilter,
65+
useLogin,
66+
loggedIn,
67+
requireAccessControl,
68+
className,
69+
onChange,
70+
shouldStream,
71+
useSuggestFollowupQuestions,
72+
promptTemplatePrefix,
73+
promptTemplateSuffix,
74+
showSuggestFollowupQuestions
75+
}: SettingsProps) => {
76+
const { t } = useTranslation();
77+
78+
// Form field IDs
79+
const promptTemplateId = useId("promptTemplate");
80+
const promptTemplateFieldId = useId("promptTemplateField");
81+
const temperatureId = useId("temperature");
82+
const temperatureFieldId = useId("temperatureField");
83+
const seedId = useId("seed");
84+
const seedFieldId = useId("seedField");
85+
const searchScoreId = useId("searchScore");
86+
const searchScoreFieldId = useId("searchScoreField");
87+
const rerankerScoreId = useId("rerankerScore");
88+
const rerankerScoreFieldId = useId("rerankerScoreField");
89+
const retrieveCountId = useId("retrieveCount");
90+
const retrieveCountFieldId = useId("retrieveCountField");
91+
const includeCategoryId = useId("includeCategory");
92+
const includeCategoryFieldId = useId("includeCategoryField");
93+
const excludeCategoryId = useId("excludeCategory");
94+
const excludeCategoryFieldId = useId("excludeCategoryField");
95+
const semanticRankerId = useId("semanticRanker");
96+
const semanticRankerFieldId = useId("semanticRankerField");
97+
const semanticCaptionsId = useId("semanticCaptions");
98+
const semanticCaptionsFieldId = useId("semanticCaptionsField");
99+
const useOidSecurityFilterId = useId("useOidSecurityFilter");
100+
const useOidSecurityFilterFieldId = useId("useOidSecurityFilterField");
101+
const useGroupsSecurityFilterId = useId("useGroupsSecurityFilter");
102+
const useGroupsSecurityFilterFieldId = useId("useGroupsSecurityFilterField");
103+
const shouldStreamId = useId("shouldStream");
104+
const shouldStreamFieldId = useId("shouldStreamField");
105+
const suggestFollowupQuestionsId = useId("suggestFollowupQuestions");
106+
const suggestFollowupQuestionsFieldId = useId("suggestFollowupQuestionsField");
107+
108+
const renderLabel = (props: RenderLabelType | undefined, labelId: string, fieldId: string, helpText: string) => (
109+
<HelpCallout labelId={labelId} fieldId={fieldId} helpText={helpText} label={props?.label} />
110+
);
111+
112+
return (
113+
<div className={className}>
114+
<TextField
115+
id={promptTemplateFieldId}
116+
className={styles.settingsSeparator}
117+
defaultValue={promptTemplate}
118+
label={t("labels.promptTemplate")}
119+
multiline
120+
autoAdjustHeight
121+
onChange={(_ev, val) => onChange("promptTemplate", val || "")}
122+
aria-labelledby={promptTemplateId}
123+
onRenderLabel={props => renderLabel(props, promptTemplateId, promptTemplateFieldId, t("helpTexts.promptTemplate"))}
124+
/>
125+
126+
<TextField
127+
id={temperatureFieldId}
128+
className={styles.settingsSeparator}
129+
label={t("labels.temperature")}
130+
type="number"
131+
min={0}
132+
max={1}
133+
step={0.1}
134+
defaultValue={temperature.toString()}
135+
onChange={(_ev, val) => onChange("temperature", parseFloat(val || "0"))}
136+
aria-labelledby={temperatureId}
137+
onRenderLabel={props => renderLabel(props, temperatureId, temperatureFieldId, t("helpTexts.temperature"))}
138+
/>
139+
140+
<TextField
141+
id={seedFieldId}
142+
className={styles.settingsSeparator}
143+
label={t("labels.seed")}
144+
type="text"
145+
defaultValue={seed?.toString() || ""}
146+
onChange={(_ev, val) => onChange("seed", val ? parseInt(val) : null)}
147+
aria-labelledby={seedId}
148+
onRenderLabel={props => renderLabel(props, seedId, seedFieldId, t("helpTexts.seed"))}
149+
/>
150+
151+
<TextField
152+
id={searchScoreFieldId}
153+
className={styles.settingsSeparator}
154+
label={t("labels.minimumSearchScore")}
155+
type="number"
156+
min={0}
157+
step={0.01}
158+
defaultValue={minimumSearchScore.toString()}
159+
onChange={(_ev, val) => onChange("minimumSearchScore", parseFloat(val || "0"))}
160+
aria-labelledby={searchScoreId}
161+
onRenderLabel={props => renderLabel(props, searchScoreId, searchScoreFieldId, t("helpTexts.searchScore"))}
162+
/>
163+
164+
{showSemanticRankerOption && (
165+
<TextField
166+
id={rerankerScoreFieldId}
167+
className={styles.settingsSeparator}
168+
label={t("labels.minimumRerankerScore")}
169+
type="number"
170+
min={1}
171+
max={4}
172+
step={0.1}
173+
defaultValue={minimumRerankerScore.toString()}
174+
onChange={(_ev, val) => onChange("minimumRerankerScore", parseFloat(val || "0"))}
175+
aria-labelledby={rerankerScoreId}
176+
onRenderLabel={props => renderLabel(props, rerankerScoreId, rerankerScoreFieldId, t("helpTexts.rerankerScore"))}
177+
/>
178+
)}
179+
180+
<TextField
181+
id={retrieveCountFieldId}
182+
className={styles.settingsSeparator}
183+
label={t("labels.retrieveCount")}
184+
type="number"
185+
min={1}
186+
max={50}
187+
defaultValue={retrieveCount.toString()}
188+
onChange={(_ev, val) => onChange("retrieveCount", parseInt(val || "3"))}
189+
aria-labelledby={retrieveCountId}
190+
onRenderLabel={props => renderLabel(props, retrieveCountId, retrieveCountFieldId, t("helpTexts.retrieveNumber"))}
191+
/>
192+
193+
<Dropdown
194+
id={includeCategoryFieldId}
195+
className={styles.settingsSeparator}
196+
label={t("labels.includeCategory")}
197+
selectedKey={includeCategory}
198+
onChange={(_ev?: React.FormEvent<HTMLElement | HTMLInputElement>, option?: IDropdownOption) => onChange("includeCategory", option?.key || "")}
199+
aria-labelledby={includeCategoryId}
200+
options={[
201+
{ key: "", text: t("labels.includeCategoryOptions.all") }
202+
// { key: "example", text: "Example Category" } // Add more categories as needed
203+
]}
204+
onRenderLabel={props => renderLabel(props, includeCategoryId, includeCategoryFieldId, t("helpTexts.includeCategory"))}
205+
/>
206+
207+
<TextField
208+
id={excludeCategoryFieldId}
209+
className={styles.settingsSeparator}
210+
label={t("labels.excludeCategory")}
211+
defaultValue={excludeCategory}
212+
onChange={(_ev, val) => onChange("excludeCategory", val || "")}
213+
aria-labelledby={excludeCategoryId}
214+
onRenderLabel={props => renderLabel(props, excludeCategoryId, excludeCategoryFieldId, t("helpTexts.excludeCategory"))}
215+
/>
216+
217+
{showSemanticRankerOption && (
218+
<>
219+
<Checkbox
220+
id={semanticRankerFieldId}
221+
className={styles.settingsSeparator}
222+
checked={useSemanticRanker}
223+
label={t("labels.useSemanticRanker")}
224+
onChange={(_ev, checked) => onChange("useSemanticRanker", !!checked)}
225+
aria-labelledby={semanticRankerId}
226+
onRenderLabel={props => renderLabel(props, semanticRankerId, semanticRankerFieldId, t("helpTexts.useSemanticReranker"))}
227+
/>
228+
229+
<Checkbox
230+
id={semanticCaptionsFieldId}
231+
className={styles.settingsSeparator}
232+
checked={useSemanticCaptions}
233+
label={t("labels.useSemanticCaptions")}
234+
onChange={(_ev, checked) => onChange("useSemanticCaptions", !!checked)}
235+
disabled={!useSemanticRanker}
236+
aria-labelledby={semanticCaptionsId}
237+
onRenderLabel={props => renderLabel(props, semanticCaptionsId, semanticCaptionsFieldId, t("helpTexts.useSemanticCaptions"))}
238+
/>
239+
</>
240+
)}
241+
242+
{useLogin && (
243+
<>
244+
<Checkbox
245+
id={useOidSecurityFilterFieldId}
246+
className={styles.settingsSeparator}
247+
checked={useOidSecurityFilter || requireAccessControl}
248+
label={t("labels.useOidSecurityFilter")}
249+
disabled={!loggedIn || requireAccessControl}
250+
onChange={(_ev, checked) => onChange("useOidSecurityFilter", !!checked)}
251+
aria-labelledby={useOidSecurityFilterId}
252+
onRenderLabel={props => renderLabel(props, useOidSecurityFilterId, useOidSecurityFilterFieldId, t("helpTexts.useOidSecurityFilter"))}
253+
/>
254+
<Checkbox
255+
id={useGroupsSecurityFilterFieldId}
256+
className={styles.settingsSeparator}
257+
checked={useGroupsSecurityFilter || requireAccessControl}
258+
label={t("labels.useGroupsSecurityFilter")}
259+
disabled={!loggedIn || requireAccessControl}
260+
onChange={(_ev, checked) => onChange("useGroupsSecurityFilter", !!checked)}
261+
aria-labelledby={useGroupsSecurityFilterId}
262+
onRenderLabel={props =>
263+
renderLabel(props, useGroupsSecurityFilterId, useGroupsSecurityFilterFieldId, t("helpTexts.useGroupsSecurityFilter"))
264+
}
265+
/>
266+
</>
267+
)}
268+
269+
{showGPT4VOptions && (
270+
<GPT4VSettings
271+
gpt4vInputs={gpt4vInput}
272+
isUseGPT4V={useGPT4V}
273+
updateUseGPT4V={val => onChange("useGPT4V", val)}
274+
updateGPT4VInputs={val => onChange("gpt4vInput", val)}
275+
/>
276+
)}
277+
278+
{showVectorOption && (
279+
<VectorSettings
280+
defaultRetrievalMode={retrievalMode}
281+
showImageOptions={useGPT4V && showGPT4VOptions}
282+
updateVectorFields={val => onChange("vectorFieldList", val)}
283+
updateRetrievalMode={val => onChange("retrievalMode", val)}
284+
/>
285+
)}
286+
287+
{/* Streaming checkbox for Chat */}
288+
{shouldStream !== undefined && (
289+
<Checkbox
290+
id={shouldStreamFieldId}
291+
className={styles.settingsSeparator}
292+
checked={shouldStream}
293+
label={t("labels.shouldStream")}
294+
onChange={(_ev, checked) => onChange("shouldStream", !!checked)}
295+
aria-labelledby={shouldStreamId}
296+
onRenderLabel={props => renderLabel(props, shouldStreamId, shouldStreamFieldId, t("helpTexts.streamChat"))}
297+
/>
298+
)}
299+
300+
{/* Followup questions checkbox for Chat */}
301+
{showSuggestFollowupQuestions && (
302+
<Checkbox
303+
id={suggestFollowupQuestionsFieldId}
304+
className={styles.settingsSeparator}
305+
checked={useSuggestFollowupQuestions}
306+
label={t("labels.useSuggestFollowupQuestions")}
307+
onChange={(_ev, checked) => onChange("useSuggestFollowupQuestions", !!checked)}
308+
aria-labelledby={suggestFollowupQuestionsId}
309+
onRenderLabel={props =>
310+
renderLabel(props, suggestFollowupQuestionsId, suggestFollowupQuestionsFieldId, t("helpTexts.suggestFollowupQuestions"))
311+
}
312+
/>
313+
)}
314+
</div>
315+
);
316+
};

0 commit comments

Comments
 (0)