Skip to content

Commit 298193a

Browse files
authored
Merge pull request #38 from Azure-Samples/howie/load-chat-history
load chat history if browser is is reloaded
2 parents bc4d716 + 077ccaa commit 298193a

File tree

4 files changed

+113
-29
lines changed

4 files changed

+113
-29
lines changed

src/api/routes.py

+76-23
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,17 @@
1313
from fastapi.templating import Jinja2Templates
1414

1515
from azure.ai.projects.aio import AIProjectClient
16+
from fastapi.responses import JSONResponse
1617
from azure.ai.projects.models import (
1718
Agent,
1819
MessageDeltaChunk,
1920
ThreadMessage,
2021
ThreadRun,
2122
AsyncAgentEventHandler,
23+
OpenAIPageableListOfThreadMessage,
24+
MessageTextContent,
25+
MessageTextFileCitationAnnotation,
26+
MessageTextUrlCitationAnnotation,
2227
RunStep
2328
)
2429

@@ -47,7 +52,28 @@ def get_agent(request: Request) -> Agent:
4752
def serialize_sse_event(data: Dict) -> str:
4853
return f"data: {json.dumps(data)}\n\n"
4954

50-
55+
async def get_message_and_annotations(ai_client : AIProjectClient, message: ThreadMessage) -> Dict:
56+
annotations = []
57+
# Get file annotations for the file search.
58+
for annotation in (a.as_dict() for a in message.file_citation_annotations):
59+
file_id = annotation["file_citation"]["file_id"]
60+
logger.info(f"Fetching file with ID for annotation {file_id}")
61+
openai_file = await ai_client.agents.get_file(file_id)
62+
annotation["file_name"] = openai_file.filename
63+
logger.info(f"File name for annotation: {annotation['file_name']}")
64+
annotations.append(annotation)
65+
66+
# Get url annotation for the index search.
67+
for url_annotation in message.url_citation_annotations:
68+
annotation = url_annotation.as_dict()
69+
annotation["file_name"] = annotation['url_citation']['title']
70+
logger.info(f"File name for annotation: {annotation['file_name']}")
71+
annotations.append(annotation)
72+
73+
return {
74+
'content': message.text_messages[0].text.value,
75+
'annotations': annotations
76+
}
5177
class MyEventHandler(AsyncAgentEventHandler[str]):
5278
def __init__(self, ai_client: AIProjectClient):
5379
super().__init__()
@@ -64,28 +90,9 @@ async def on_thread_message(self, message: ThreadMessage) -> Optional[str]:
6490
return None
6591

6692
logger.info("MyEventHandler: Received completed message")
67-
annotations = []
68-
# Get file annotations for the file search.
69-
for annotation in (a.as_dict() for a in message.file_citation_annotations):
70-
file_id = annotation["file_citation"]["file_id"]
71-
logger.info(f"Fetching file with ID for annotation {file_id}")
72-
openai_file = await self.ai_client.agents.get_file(file_id)
73-
annotation["file_name"] = openai_file.filename
74-
logger.info(f"File name for annotation: {annotation['file_name']}")
75-
annotations.append(annotation)
76-
77-
# Get url annotation for the index search.
78-
for url_annotation in message.url_citation_annotations:
79-
annotation = url_annotation.as_dict()
80-
annotation["file_name"] = annotation['url_citation']['title']
81-
logger.info(f"File name for annotation: {annotation['file_name']}")
82-
annotations.append(annotation)
83-
84-
stream_data = {
85-
'content': message.text_messages[0].text.value,
86-
'annotations': annotations,
87-
'type': "completed_message"
88-
}
93+
94+
stream_data = await get_message_and_annotations(self.ai_client, message)
95+
stream_data['type'] = "completed_message"
8996
return serialize_sse_event(stream_data)
9097
except Exception as e:
9198
logger.error(f"Error in event handler for thread message: {e}", exc_info=True)
@@ -150,6 +157,52 @@ async def get_result(thread_id: str, agent_id: str, ai_client : AIProjectClient)
150157
yield serialize_sse_event({'type': "error", 'message': str(e)})
151158

152159

160+
@router.get("/chat/history")
161+
async def history(
162+
request: Request,
163+
ai_client : AIProjectClient = Depends(get_ai_client),
164+
agent : Agent = Depends(get_agent),
165+
):
166+
# Retrieve the thread ID from the cookies (if available).
167+
thread_id = request.cookies.get('thread_id')
168+
agent_id = request.cookies.get('agent_id')
169+
170+
# Attempt to get an existing thread. If not found, create a new one.
171+
try:
172+
if thread_id and agent_id == agent.id:
173+
logger.info(f"Retrieving thread with ID {thread_id}")
174+
thread = await ai_client.agents.get_thread(thread_id)
175+
else:
176+
logger.info("Creating a new thread")
177+
thread = await ai_client.agents.create_thread()
178+
except Exception as e:
179+
logger.error(f"Error handling thread: {e}")
180+
raise HTTPException(status_code=400, detail=f"Error handling thread: {e}")
181+
182+
thread_id = thread.id
183+
messages = OpenAIPageableListOfThreadMessage()
184+
185+
# Create a new message from the user's input.
186+
try:
187+
content = []
188+
response = await ai_client.agents.list_messages(
189+
thread_id=thread_id,
190+
)
191+
for message in response.data:
192+
content.append(await get_message_and_annotations(ai_client, message))
193+
194+
logger.info(f"List message, thread ID: {thread_id}")
195+
response = JSONResponse(content=content)
196+
197+
# Update cookies to persist the thread and agent IDs.
198+
response.set_cookie("thread_id", thread_id)
199+
response.set_cookie("agent_id", agent_id)
200+
return response
201+
except Exception as e:
202+
logger.error(f"Error listing message: {e}")
203+
raise HTTPException(status_code=500, detail=f"Error list message: {e}")
204+
205+
153206
@router.post("/chat")
154207
async def chat(
155208
request: Request,

src/api/static/ChatUI.js

+29-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ class ChatUI {
100100
const chatColumn = document.getElementById("chat-container");
101101

102102
// Hide the document viewer and the close button
103-
docViewerSection.style.display = 'none';
103+
docViewerSection.style.setProperty("display", "none", "important");
104104
docViewerSection.classList.add("hidden");
105105
docViewerSection.classList.remove("visible");
106106
document.getElementById("close-button").style.display = 'none';
@@ -207,6 +207,34 @@ class ChatUI {
207207
inputBox.scrollIntoView({ behavior: 'smooth', block: 'end' });
208208
}
209209
}
210+
211+
async loadChatHistory() {
212+
var response = await fetch("/chat/history", {
213+
method: 'GET',
214+
headers: {
215+
'Content-Type': 'application/json'
216+
},
217+
credentials: 'include'
218+
});
219+
220+
if (response.ok) {
221+
var json_response = await response.json();
222+
223+
const reversedResponse = [...json_response].reverse();
224+
for (let response of reversedResponse) {
225+
var msg = response.content;
226+
if (message.role === "user") {
227+
this.appendUserMessage(msg);
228+
} else {
229+
var messageDiv = this.createAssistantMessageDiv();
230+
this.appendAssistantMessage(messageDiv, msg, false, response.annotations);
231+
}
232+
}
233+
} else {
234+
var messageDiv = this.createAssistantMessageDiv();
235+
this.appendAssistantMessage(messageDiv, "Error occurs while loading chat history!", false, []);
236+
}
237+
}
210238
}
211239

212240
export default ChatUI;

src/api/static/main.js

+7-4
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
import ChatUI from './ChatUI.js';
55
import ChatClient from './ChatClient.js';
66

7-
function initChat() {
8-
const chatUI = new ChatUI();
9-
const chatClient = new ChatClient(chatUI);
7+
const chatUI = new ChatUI();
8+
const chatClient = new ChatClient(chatUI);
109

10+
function initChat() {
11+
1112
const form = document.getElementById("chat-form");
1213
const messageInput = document.getElementById("message");
1314
const targetContainer = document.getElementById("messages");
@@ -30,4 +31,6 @@ function initChat() {
3031
};
3132
}
3233

33-
document.addEventListener("DOMContentLoaded", initChat);
34+
document.addEventListener("DOMContentLoaded", initChat);
35+
36+
await chatUI.loadChatHistory();

src/api/templates/index.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
</div>
6767

6868
<!-- Document Viewer Section -->
69-
<div class="col-6 d-flex flex-column h-100 p-3 bg-light border-left" id="document-viewer-section" style="display: none;"> <!-- Hidden and no space initially -->
69+
<div class="col-6 d-flex flex-column h-100 p-3 bg-light border-left" id="document-viewer-section" style="display: none !important;"> <!-- Hidden and no space initially -->
7070
<div class="d-flex justify-content-between align-items-center mb-2">
7171
<h5 class="mb-0">Document Viewer</h5>
7272
<button id="close-button" class="btn btn-danger">Close</button>

0 commit comments

Comments
 (0)