Skip to content

Commit f59fce4

Browse files
committed
Add chat history feature
This commit adds the following new files: - HistoryItem: A new component for displaying chat history items. - HistoryPanel: A new component for displaying the chat history panel. - HistoryButton: A new component for opening the chat history. - HistoryProviders: A new module containing history provider classes. These changes include functionality to support future enhancements to the chat history feature.
1 parent 0225f75 commit f59fce4

23 files changed

+638
-6
lines changed

app/backend/app.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
CONFIG_AUTH_CLIENT,
6060
CONFIG_BLOB_CONTAINER_CLIENT,
6161
CONFIG_CHAT_APPROACH,
62+
CONFIG_CHAT_HISTORY_ENABLED,
6263
CONFIG_CHAT_VISION_APPROACH,
6364
CONFIG_CREDENTIAL,
6465
CONFIG_GPT4V_DEPLOYED,
@@ -276,6 +277,7 @@ def config():
276277
"showSpeechInput": current_app.config[CONFIG_SPEECH_INPUT_ENABLED],
277278
"showSpeechOutputBrowser": current_app.config[CONFIG_SPEECH_OUTPUT_BROWSER_ENABLED],
278279
"showSpeechOutputAzure": current_app.config[CONFIG_SPEECH_OUTPUT_AZURE_ENABLED],
280+
"showChatHistory": current_app.config[CONFIG_CHAT_HISTORY_ENABLED],
279281
}
280282
)
281283

@@ -439,6 +441,7 @@ async def setup_clients():
439441
USE_SPEECH_INPUT_BROWSER = os.getenv("USE_SPEECH_INPUT_BROWSER", "").lower() == "true"
440442
USE_SPEECH_OUTPUT_BROWSER = os.getenv("USE_SPEECH_OUTPUT_BROWSER", "").lower() == "true"
441443
USE_SPEECH_OUTPUT_AZURE = os.getenv("USE_SPEECH_OUTPUT_AZURE", "").lower() == "true"
444+
USE_CHAT_HISTORY = os.getenv("USE_CHAT_HISTORY", "").lower() == "true"
442445

443446
# WEBSITE_HOSTNAME is always set by App Service, RUNNING_IN_PRODUCTION is set in main.bicep
444447
RUNNING_ON_AZURE = os.getenv("WEBSITE_HOSTNAME") is not None or os.getenv("RUNNING_IN_PRODUCTION") is not None
@@ -609,6 +612,7 @@ async def setup_clients():
609612
current_app.config[CONFIG_SPEECH_INPUT_ENABLED] = USE_SPEECH_INPUT_BROWSER
610613
current_app.config[CONFIG_SPEECH_OUTPUT_BROWSER_ENABLED] = USE_SPEECH_OUTPUT_BROWSER
611614
current_app.config[CONFIG_SPEECH_OUTPUT_AZURE_ENABLED] = USE_SPEECH_OUTPUT_AZURE
615+
current_app.config[CONFIG_CHAT_HISTORY_ENABLED] = USE_CHAT_HISTORY
612616

613617
# Various approaches to integrate GPT and external knowledge, most applications will use a single one of these patterns
614618
# or some derivative, here we include several for exploration purposes

app/backend/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@
2222
CONFIG_SPEECH_SERVICE_LOCATION = "speech_service_location"
2323
CONFIG_SPEECH_SERVICE_TOKEN = "speech_service_token"
2424
CONFIG_SPEECH_SERVICE_VOICE = "speech_service_voice"
25+
CONFIG_CHAT_HISTORY_ENABLED = "chat_history_enabled"

app/frontend/package-lock.json

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"i18next": "^23.12.2",
2323
"i18next-browser-languagedetector": "^8.0.0",
2424
"i18next-http-backend": "^2.5.2",
25+
"idb": "^8.0.0",
2526
"ndjson-readablestream": "^1.2.0",
2627
"react": "^18.3.1",
2728
"react-dom": "^18.3.1",

app/frontend/src/api/models.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ export type Config = {
8989
showSpeechInput: boolean;
9090
showSpeechOutputBrowser: boolean;
9191
showSpeechOutputAzure: boolean;
92+
showChatHistory: boolean;
9293
};
9394

9495
export type SimpleAPIResponse = {
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.container {
2+
display: flex;
3+
align-items: center;
4+
gap: 0.375em;
5+
cursor: pointer;
6+
padding: 0.5rem;
7+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { History24Regular } from "@fluentui/react-icons";
2+
import { Button } from "@fluentui/react-components";
3+
import { useTranslation } from "react-i18next";
4+
5+
import styles from "./HistoryButton.module.css";
6+
7+
interface Props {
8+
className?: string;
9+
onClick: () => void;
10+
disabled?: boolean;
11+
}
12+
13+
export const HistoryButton = ({ className, disabled, onClick }: Props) => {
14+
const { t } = useTranslation();
15+
return (
16+
<div className={`${styles.container} ${className ?? ""}`}>
17+
<Button icon={<History24Regular />} disabled={disabled} onClick={onClick}>
18+
{t("history.openChatHistory")}
19+
</Button>
20+
</div>
21+
);
22+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./HistoryButton";
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
.historyItem {
2+
display: flex;
3+
align-items: center;
4+
justify-content: space-between;
5+
padding: 4px 8px;
6+
border-radius: 6px;
7+
transition: background-color 0.2s;
8+
}
9+
10+
.historyItem:hover {
11+
background-color: #f3f4f6;
12+
}
13+
14+
.historyItemButton {
15+
flex-grow: 1;
16+
text-align: left;
17+
padding: 0;
18+
margin-right: 4px;
19+
background: none;
20+
border: none;
21+
cursor: pointer;
22+
}
23+
24+
.historyItemTitle {
25+
font-size: 1rem;
26+
}
27+
28+
.deleteIcon {
29+
width: 20px;
30+
height: 20px;
31+
}
32+
33+
.deleteButton {
34+
opacity: 0;
35+
transition: opacity 0.2s;
36+
background: none;
37+
border: none;
38+
cursor: pointer;
39+
padding: 4px;
40+
border-radius: 9999px;
41+
color: #6b7280;
42+
}
43+
44+
.historyItem:hover .deleteButton,
45+
.deleteButton:focus {
46+
opacity: 1;
47+
}
48+
49+
.deleteButton:hover {
50+
color: #111827;
51+
}
52+
53+
.modalOverlay {
54+
position: fixed;
55+
top: 0;
56+
left: 0;
57+
right: 0;
58+
bottom: 0;
59+
background-color: rgba(0, 0, 0, 0.5);
60+
display: flex;
61+
align-items: center;
62+
justify-content: center;
63+
z-index: 50;
64+
}
65+
66+
.modalContent {
67+
background-color: white;
68+
padding: 24px;
69+
border-radius: 8px;
70+
box-shadow:
71+
0 4px 6px -1px rgba(0, 0, 0, 0.1),
72+
0 2px 4px -1px rgba(0, 0, 0, 0.06);
73+
max-width: 400px;
74+
width: 100%;
75+
}
76+
77+
.modalTitle {
78+
font-size: 20px;
79+
font-weight: 600;
80+
margin-top: 0px;
81+
margin-bottom: 16px;
82+
}
83+
84+
.modalDescription {
85+
margin-top: 0px;
86+
margin-bottom: 16px;
87+
}
88+
89+
.modalActions {
90+
display: flex;
91+
justify-content: flex-end;
92+
gap: 16px;
93+
}
94+
95+
.modalCancelButton,
96+
.modalConfirmButton {
97+
padding: 8px 16px;
98+
border-radius: 4px;
99+
font-size: 14px;
100+
font-weight: 500;
101+
cursor: pointer;
102+
}
103+
104+
.modalCancelButton {
105+
background-color: #f3f4f6;
106+
color: #374151;
107+
}
108+
109+
.modalConfirmButton {
110+
background-color: #ef4444;
111+
color: white;
112+
}
113+
114+
.modalCancelButton:hover {
115+
background-color: #e5e7eb;
116+
}
117+
118+
.modalConfirmButton:hover {
119+
background-color: #dc2626;
120+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { useState, useCallback } from "react";
2+
import { useTranslation } from "react-i18next";
3+
import styles from "./HistoryItem.module.css";
4+
import { DefaultButton } from "@fluentui/react";
5+
import { Delete24Regular } from "@fluentui/react-icons";
6+
7+
export interface HistoryData {
8+
id: string;
9+
title: string;
10+
timestamp: number;
11+
}
12+
13+
interface HistoryItemProps {
14+
item: HistoryData;
15+
onSelect: (id: string) => void;
16+
onDelete: (id: string) => void;
17+
}
18+
19+
export function HistoryItem({ item, onSelect, onDelete }: HistoryItemProps) {
20+
const [isModalOpen, setIsModalOpen] = useState(false);
21+
22+
const handleDelete = useCallback(() => {
23+
setIsModalOpen(false);
24+
onDelete(item.id);
25+
}, [item.id, onDelete]);
26+
27+
return (
28+
<div className={styles.historyItem}>
29+
<button onClick={() => onSelect(item.id)} className={styles.historyItemButton}>
30+
<div className={styles.historyItemTitle}>{item.title}</div>
31+
</button>
32+
<button onClick={() => setIsModalOpen(true)} className={styles.deleteButton} aria-label="delete this chat history">
33+
<Delete24Regular className={styles.deleteIcon} />
34+
</button>
35+
<DeleteHistoryModal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)} onConfirm={handleDelete} />
36+
</div>
37+
);
38+
}
39+
40+
function DeleteHistoryModal({ isOpen, onClose, onConfirm }: { isOpen: boolean; onClose: () => void; onConfirm: () => void }) {
41+
if (!isOpen) return null;
42+
const { t } = useTranslation();
43+
return (
44+
<div className={styles.modalOverlay}>
45+
<div className={styles.modalContent}>
46+
<h2 className={styles.modalTitle}>{t("history.deleteModalTitle")}</h2>
47+
<p className={styles.modalDescription}>{t("history.deleteModalDescription")}</p>
48+
<div className={styles.modalActions}>
49+
<DefaultButton onClick={onClose} className={styles.modalCancelButton}>
50+
{t("history.cancelLabel")}
51+
</DefaultButton>
52+
<DefaultButton onClick={onConfirm} className={styles.modalConfirmButton}>
53+
{t("history.deleteLabel")}
54+
</DefaultButton>
55+
</div>
56+
</div>
57+
</div>
58+
);
59+
}

0 commit comments

Comments
 (0)