Skip to content

Commit cbe42e7

Browse files
committed
Add AIModeToggle component to chat UI
Introduces a new AIModeToggle component for switching AI modes directly in the chat interface. Refactors AIModeSettings to use currentAIMode prop and updates Settings and Chat components to support the new toggle. Adds related styles for improved layout and visual feedback.
1 parent 5071cc2 commit cbe42e7

File tree

7 files changed

+155
-13
lines changed

7 files changed

+155
-13
lines changed

app/frontend/src/components/AIModeSettings/AIModeSettings.tsx

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { useState } from "react";
21
import { Stack } from "@fluentui/react";
32
import { useId } from "@fluentui/react-hooks";
43
import { useTranslation } from "react-i18next";
@@ -8,19 +7,17 @@ import { HelpCallout } from "../../components/HelpCallout";
87
import { AIMode } from "../../api";
98

109
interface Props {
11-
defaultAIMode: AIMode;
10+
currentAIMode: AIMode;
1211
updateAIMode: (aiMode: AIMode) => void;
1312
}
1413

15-
export const AIModeSettings = ({ defaultAIMode, updateAIMode }: Props) => {
16-
const [aiMode, setAIMode] = useState<AIMode>(defaultAIMode || AIMode.DataAndOpenAI);
14+
export const AIModeSettings = ({ currentAIMode, updateAIMode }: Props) => {
1715
const { t } = useTranslation();
1816

1917
const aiModeId = useId("aiMode");
2018
const aiModeFieldId = useId("aiModeField");
2119

2220
const handleModeChange = (selectedMode: AIMode) => {
23-
setAIMode(selectedMode);
2421
updateAIMode(selectedMode);
2522
};
2623

@@ -43,27 +40,27 @@ export const AIModeSettings = ({ defaultAIMode, updateAIMode }: Props) => {
4340

4441
<div className={styles.optionSlider}>
4542
<div className={styles.sliderTrack}>
46-
<div className={styles.sliderThumb} style={{ left: getModePosition(aiMode) }}>
47-
{aiMode === AIMode.DataOnly && t("labels.aiMode.options.dataOnly")}
48-
{aiMode === AIMode.OpenAIOnly && t("labels.aiMode.options.openaiOnly")}
49-
{aiMode === AIMode.DataAndOpenAI && t("labels.aiMode.options.dataAndOpenai")}
43+
<div className={styles.sliderThumb} style={{ left: getModePosition(currentAIMode) }}>
44+
{currentAIMode === AIMode.DataOnly && t("labels.aiMode.options.dataOnly")}
45+
{currentAIMode === AIMode.OpenAIOnly && t("labels.aiMode.options.openaiOnly")}
46+
{currentAIMode === AIMode.DataAndOpenAI && t("labels.aiMode.options.dataAndOpenai")}
5047
</div>
5148

5249
<div className={styles.sliderOptions}>
5350
<div
54-
className={`${styles.sliderOption} ${aiMode === AIMode.DataOnly ? styles.active : ""}`}
51+
className={`${styles.sliderOption} ${currentAIMode === AIMode.DataOnly ? styles.active : ""}`}
5552
onClick={() => handleModeChange(AIMode.DataOnly)}
5653
>
5754
{t("labels.aiMode.options.dataOnly")}
5855
</div>
5956
<div
60-
className={`${styles.sliderOption} ${aiMode === AIMode.OpenAIOnly ? styles.active : ""}`}
57+
className={`${styles.sliderOption} ${currentAIMode === AIMode.OpenAIOnly ? styles.active : ""}`}
6158
onClick={() => handleModeChange(AIMode.OpenAIOnly)}
6259
>
6360
{t("labels.aiMode.options.openaiOnly")}
6461
</div>
6562
<div
66-
className={`${styles.sliderOption} ${aiMode === AIMode.DataAndOpenAI ? styles.active : ""}`}
63+
className={`${styles.sliderOption} ${currentAIMode === AIMode.DataAndOpenAI ? styles.active : ""}`}
6764
onClick={() => handleModeChange(AIMode.DataAndOpenAI)}
6865
>
6966
{t("labels.aiMode.options.dataAndOpenai")}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
.aiModeToggle {
2+
display: flex;
3+
gap: 8px;
4+
align-items: center;
5+
margin-left: 205px;
6+
}
7+
8+
.optionSlider {
9+
width: 300px;
10+
position: relative;
11+
user-select: none;
12+
}
13+
14+
.sliderTrack {
15+
width: 100%;
16+
height: 32px;
17+
background: #e0e0e0;
18+
border-radius: 16px;
19+
position: relative;
20+
}
21+
22+
.sliderThumb {
23+
position: absolute;
24+
top: 0;
25+
width: 33.33%;
26+
height: 32px;
27+
background: #0078d4;
28+
border-radius: 16px;
29+
color: #fff;
30+
text-align: center;
31+
line-height: 32px;
32+
transition: left 0.3s ease;
33+
cursor: pointer;
34+
font-weight: bold;
35+
font-size: 11px;
36+
z-index: 2;
37+
}
38+
39+
.sliderOptions {
40+
display: flex;
41+
justify-content: space-between;
42+
position: absolute;
43+
top: 0;
44+
width: 100%;
45+
height: 32px;
46+
}
47+
48+
.sliderOption {
49+
width: 33.33%;
50+
text-align: center;
51+
line-height: 32px;
52+
color: #666;
53+
cursor: pointer;
54+
font-size: 11px;
55+
font-weight: 500;
56+
z-index: 1;
57+
transition: color 0.3s ease;
58+
}
59+
60+
.sliderOption:hover {
61+
color: #0078d4;
62+
}
63+
64+
.sliderOption.active {
65+
font-weight: bold;
66+
color: #fff;
67+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { useTranslation } from "react-i18next";
2+
import { AIMode } from "../../api";
3+
import styles from "./AIModeToggle.module.css";
4+
5+
interface Props {
6+
currentAIMode: AIMode;
7+
updateAIMode: (aiMode: AIMode) => void;
8+
}
9+
10+
export const AIModeToggle = ({ currentAIMode, updateAIMode }: Props) => {
11+
const { t } = useTranslation();
12+
13+
const handleModeChange = (selectedMode: AIMode) => {
14+
updateAIMode(selectedMode);
15+
};
16+
17+
const getModePosition = (mode: AIMode): string => {
18+
switch (mode) {
19+
case AIMode.DataOnly:
20+
return "0%";
21+
case AIMode.OpenAIOnly:
22+
return "33.33%";
23+
case AIMode.DataAndOpenAI:
24+
return "66.67%";
25+
default:
26+
return "66.67%";
27+
}
28+
};
29+
30+
return (
31+
<div className={styles.aiModeToggle}>
32+
<div className={styles.optionSlider}>
33+
<div className={styles.sliderTrack}>
34+
<div className={styles.sliderThumb} style={{ left: getModePosition(currentAIMode) }}>
35+
{currentAIMode === AIMode.DataOnly && t("labels.aiMode.options.dataOnly")}
36+
{currentAIMode === AIMode.OpenAIOnly && t("labels.aiMode.options.openaiOnly")}
37+
{currentAIMode === AIMode.DataAndOpenAI && t("labels.aiMode.options.dataAndOpenai")}
38+
</div>
39+
40+
<div className={styles.sliderOptions}>
41+
<div
42+
className={`${styles.sliderOption} ${currentAIMode === AIMode.DataOnly ? styles.active : ""}`}
43+
onClick={() => handleModeChange(AIMode.DataOnly)}
44+
>
45+
{t("labels.aiMode.options.dataOnly")}
46+
</div>
47+
<div
48+
className={`${styles.sliderOption} ${currentAIMode === AIMode.OpenAIOnly ? styles.active : ""}`}
49+
onClick={() => handleModeChange(AIMode.OpenAIOnly)}
50+
>
51+
{t("labels.aiMode.options.openaiOnly")}
52+
</div>
53+
<div
54+
className={`${styles.sliderOption} ${currentAIMode === AIMode.DataAndOpenAI ? styles.active : ""}`}
55+
onClick={() => handleModeChange(AIMode.DataAndOpenAI)}
56+
>
57+
{t("labels.aiMode.options.dataAndOpenai")}
58+
</div>
59+
</div>
60+
</div>
61+
</div>
62+
</div>
63+
);
64+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { AIModeToggle } from "./AIModeToggle";

app/frontend/src/components/Settings/Settings.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ export const Settings = ({
151151
onRenderLabel={props => renderLabel(props, promptTemplateId, promptTemplateFieldId, t("helpTexts.promptTemplate"))}
152152
/>
153153

154-
<AIModeSettings defaultAIMode={aiMode} updateAIMode={val => onChange("aiMode", val)} />
154+
<AIModeSettings currentAIMode={aiMode} updateAIMode={val => onChange("aiMode", val)} />
155155

156156
<TextField
157157
id={temperatureFieldId}

app/frontend/src/pages/chat/Chat.module.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
.commandsSplitContainer {
9696
display: flex;
9797
justify-content: space-between;
98+
align-items: center;
9899
}
99100

100101
.commandsContainer {
@@ -104,6 +105,14 @@
104105
align-self: flex-end;
105106
}
106107

108+
.commandsCenter {
109+
display: flex;
110+
justify-content: center;
111+
align-items: center;
112+
flex: 1;
113+
margin-left: 85px;
114+
}
115+
107116
.commandButton {
108117
margin-bottom: 1.25rem;
109118
}

app/frontend/src/pages/chat/Chat.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { HistoryButton } from "../../components/HistoryButton";
3131
import { SettingsButton } from "../../components/SettingsButton";
3232
import { ClearChatButton } from "../../components/ClearChatButton";
3333
import { UploadFile } from "../../components/UploadFile";
34+
import { AIModeToggle } from "../../components/AIModeToggle";
3435
import { useLogin, getToken, requireAccessControl } from "../../authConfig";
3536
import { useMsal } from "@azure/msal-react";
3637
import { TokenClaimsDisplay } from "../../components/TokenClaimsDisplay";
@@ -413,6 +414,9 @@ const Chat = () => {
413414
<HistoryButton className={styles.commandButton} onClick={() => setIsHistoryPanelOpen(!isHistoryPanelOpen)} />
414415
)}
415416
</div>
417+
<div className={styles.commandsCenter}>
418+
<AIModeToggle currentAIMode={aiMode} updateAIMode={setAIMode} />
419+
</div>
416420
<div className={styles.commandsContainer}>
417421
<ClearChatButton className={styles.commandButton} onClick={clearChat} disabled={!lastQuestionRef.current || isLoading} />
418422
{showUserUpload && <UploadFile className={styles.commandButton} disabled={!loggedIn} />}

0 commit comments

Comments
 (0)