Skip to content

Commit 09dec32

Browse files
authored
fix: show feedback button only after request finished (#60)
* Revert "fix: fixed chat history click issue (#59)" This reverts commit 862a8e5. * fix: fixed feedback buttons when request is in progress and messages storage
1 parent 862a8e5 commit 09dec32

4 files changed

Lines changed: 169 additions & 64 deletions

File tree

src/core/ChatWidget.ts

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,8 @@ export class ChatWidgetCore {
538538
if (messageIndex !== -1) {
539539
chat.messages[messageIndex] = assistantMessage;
540540
this.storage.updateChat(chat);
541+
// Update the history panel's local state to keep UI in sync
542+
this.historyPanel.updateChatInLocalState(chat);
541543
}
542544
}
543545

@@ -744,6 +746,9 @@ export class ChatWidgetCore {
744746
chatContainer.style.display = 'flex';
745747
this.isOpen = true;
746748

749+
// Refresh history panel from storage to ensure it's up to date
750+
this.historyPanel.refreshFromStorage();
751+
747752
// Change the toggle button icon to a cross
748753
if (toggleButton) {
749754
toggleButton.innerHTML =
@@ -792,6 +797,7 @@ export class ChatWidgetCore {
792797
sender: 'user',
793798
timestamp: Date.now(),
794799
files: [...this.selectedFiles], // Include any selected files
800+
isComplete: true, // User messages are always complete when created
795801
};
796802

797803
this.hideWelcomeScreen();
@@ -1081,6 +1087,9 @@ export class ChatWidgetCore {
10811087
return;
10821088
}
10831089

1090+
// Mark the message as complete
1091+
lastMessage.isComplete = true;
1092+
10841093
// Process pending contracts and update images (now async)
10851094
await this.processContractImages(lastMessage.id);
10861095

@@ -1097,11 +1106,22 @@ export class ChatWidgetCore {
10971106
text: lastMessage.text,
10981107
intermediateSteps: lastMessage.intermediateSteps,
10991108
processedImages: lastMessage.processedImages,
1109+
isComplete: true, // Ensure it's marked as complete in storage too
11001110
};
11011111
}
11021112
return m;
11031113
});
11041114
this.storage.updateChat(chat);
1115+
// Update the history panel's local state to keep UI in sync
1116+
this.historyPanel.updateChatInLocalState(chat);
1117+
}
1118+
1119+
// Add feedback buttons now that the message is complete
1120+
if (
1121+
typeof this.options.onFeedback === 'function' &&
1122+
lastMessage.sender === 'bot'
1123+
) {
1124+
this.addFeedbackButtonsToMessage(lastMessage.id, lastMessage.feedback);
11051125
}
11061126

11071127
// hide the intermediate steps container
@@ -1208,6 +1228,7 @@ export class ChatWidgetCore {
12081228
text,
12091229
sender: 'bot',
12101230
timestamp: Date.now(),
1231+
isComplete: !streaming, // Complete if not streaming, incomplete if streaming
12111232
};
12121233

12131234
this.addMessage(botMessage);
@@ -1277,6 +1298,8 @@ export class ChatWidgetCore {
12771298
chat.messages.push(message);
12781299
chat.updatedAt = Date.now();
12791300
this.storage.updateChat(chat);
1301+
// Update the history panel's local state to keep UI in sync
1302+
this.historyPanel.updateChatInLocalState(chat);
12801303
}
12811304
}
12821305

@@ -1380,10 +1403,11 @@ export class ChatWidgetCore {
13801403
contentContainer.appendChild(mainMessageContentWrapper);
13811404
contentContainer.appendChild(timestamp);
13821405

1383-
// Add feedback buttons for bot messages
1406+
// Add feedback buttons for bot messages only after they are complete
13841407
if (
13851408
typeof this.options.onFeedback === 'function' &&
1386-
message.sender === 'bot'
1409+
message.sender === 'bot' &&
1410+
message.isComplete
13871411
) {
13881412
const feedbackContainer = UIComponents.createFeedbackButtons(
13891413
message.id,
@@ -1647,11 +1671,9 @@ export class ChatWidgetCore {
16471671
}
16481672
}
16491673

1650-
private async loadChatHistory(sessionId: string, updatedAt: number) {
1674+
private async loadChatHistory(sessionId: string) {
16511675
const chats = this.storage.getChats();
1652-
const chat = chats.find(
1653-
(h: HistoryChat) => h.sessionId === sessionId && h.updatedAt === updatedAt
1654-
);
1676+
const chat = chats.find((h: HistoryChat) => h.sessionId === sessionId);
16551677
if (chat) {
16561678
this.historyPanel.setActiveChat(chat);
16571679
this.hideWelcomeScreen();
@@ -1668,6 +1690,10 @@ export class ChatWidgetCore {
16681690
for (const message of this.messages) {
16691691
// skip empty messages since they are meaningless
16701692
if (message.text) {
1693+
// Ensure historical messages are marked as complete
1694+
if (message.isComplete === undefined) {
1695+
message.isComplete = true;
1696+
}
16711697
await this.renderMessage(message);
16721698
}
16731699
}
@@ -1719,4 +1745,38 @@ export class ChatWidgetCore {
17191745
newChatButton.disabled = !!this.abortController;
17201746
}
17211747
}
1748+
1749+
private addFeedbackButtonsToMessage(
1750+
messageId: string,
1751+
selectedFeedback?: 'positive' | 'negative'
1752+
): void {
1753+
const messageElement = this.widgetElement?.querySelector(
1754+
`#chat-message-${messageId}`
1755+
);
1756+
if (!messageElement) {
1757+
return;
1758+
}
1759+
1760+
const contentContainer = messageElement.querySelector(
1761+
'.chat-message-content'
1762+
);
1763+
if (!contentContainer) {
1764+
return;
1765+
}
1766+
1767+
// Check if feedback buttons already exist
1768+
const existingFeedback = contentContainer.querySelector(
1769+
'.chat-message-feedback'
1770+
);
1771+
if (existingFeedback) {
1772+
return;
1773+
}
1774+
1775+
// Create and add feedback buttons
1776+
const feedbackContainer = UIComponents.createFeedbackButtons(
1777+
messageId,
1778+
selectedFeedback
1779+
);
1780+
contentContainer.appendChild(feedbackContainer);
1781+
}
17221782
}

src/core/HistoryPanel.ts

Lines changed: 62 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,16 @@ import { Storage } from './Storage';
33

44
class HistoryPanel {
55
private readonly chats: HistoryChat[] = [];
6-
private readonly onChatItemClick: (
7-
sessionId: string,
8-
updatedAt: number
9-
) => void;
10-
private readonly onChatItemDelete?: (
11-
sessionId: string,
12-
updatedAt: number
13-
) => void;
6+
private readonly onChatItemClick: (sessionId: string) => void;
7+
private readonly onChatItemDelete?: (sessionId: string) => void;
148
private readonly isStreamingActive: () => boolean;
159
private chatsContainer: HTMLDivElement;
1610
private readonly storage: Storage;
1711
private activeChat: HistoryChat | null = null;
1812

1913
constructor(
20-
onChatItemClick: (sessionId: string, updatedAt: number) => void,
21-
onChatItemDelete?: (sessionId: string, updatedAt: number) => void,
14+
onChatItemClick: (sessionId: string) => void,
15+
onChatItemDelete?: (sessionId: string) => void,
2216
isStreamingActive?: () => boolean
2317
) {
2418
this.storage = Storage.getInstance();
@@ -177,18 +171,65 @@ class HistoryPanel {
177171
item.classList.remove('active');
178172
});
179173
const chatItem = this.chatsContainer.querySelector(
180-
`.chat-widget-history-chat[data-session-id="${chat.sessionId}"][data-updated-at="${chat.updatedAt}"]`
174+
`.chat-widget-history-chat[data-session-id="${chat.sessionId}"]`
181175
);
182176
if (chatItem) {
183177
chatItem.classList.add('active');
184178
}
185179
}
186180

181+
updateChatInLocalState(updatedChat: HistoryChat) {
182+
// Update local chats array
183+
const index = this.chats.findIndex(
184+
(c) => c.sessionId === updatedChat.sessionId
185+
);
186+
if (index !== -1) {
187+
this.chats[index] = updatedChat;
188+
189+
// Re-render the grouped chats to reflect changes (like new messages/updated timestamps)
190+
this.chatsContainer.innerHTML = '';
191+
this.renderGroupedChats(this.chatsContainer);
192+
193+
// Maintain active state if this was the active chat
194+
if (
195+
this.activeChat &&
196+
this.activeChat.sessionId === updatedChat.sessionId
197+
) {
198+
this.setActiveChat(updatedChat);
199+
}
200+
}
201+
}
202+
203+
refreshFromStorage() {
204+
// Refresh the local chats array from storage
205+
const freshChats = this.storage.getChats();
206+
this.chats.splice(0, this.chats.length, ...freshChats);
207+
208+
// Re-render the entire history panel
209+
this.chatsContainer.innerHTML = '';
210+
if (this.chats.length === 0) {
211+
this.chatsContainer.appendChild(this.renderEmptyState());
212+
} else {
213+
this.renderGroupedChats(this.chatsContainer);
214+
215+
// Maintain active state if we still have an active chat
216+
if (this.activeChat) {
217+
const activeChat = this.chats.find(
218+
(c) => c.sessionId === this.activeChat!.sessionId
219+
);
220+
if (activeChat) {
221+
this.setActiveChat(activeChat);
222+
} else {
223+
this.activeChat = null;
224+
}
225+
}
226+
}
227+
}
228+
187229
private renderChat(chat: HistoryChat) {
188230
const chatItem = document.createElement('div');
189231
chatItem.className = 'chat-widget-history-chat';
190232
chatItem.setAttribute('data-session-id', chat.sessionId);
191-
chatItem.setAttribute('data-updated-at', chat.updatedAt.toString());
192233

193234
// Check if streaming is active to disable the chat item
194235
const isDisabled = this.isStreamingActive();
@@ -214,7 +255,7 @@ class HistoryPanel {
214255
if (!isDisabled) {
215256
deleteBtn.addEventListener('click', (e) => {
216257
e.stopPropagation();
217-
this.deleteChat(chat.sessionId, chat.updatedAt);
258+
this.deleteChat(chat.sessionId);
218259
});
219260
} else {
220261
deleteBtn.style.pointerEvents = 'none';
@@ -224,7 +265,7 @@ class HistoryPanel {
224265

225266
if (!isDisabled) {
226267
chatItem.addEventListener('click', () =>
227-
this.onChatItemClick(chat.sessionId, chat.updatedAt)
268+
this.onChatItemClick(chat.sessionId)
228269
);
229270
}
230271
return chatItem;
@@ -246,18 +287,14 @@ class HistoryPanel {
246287
return historyEmpty;
247288
}
248289

249-
private deleteChat(sessionId: string, updatedAt: number) {
250-
// Remove from storage using both sessionId and updatedAt
251-
this.storage.removeChat(sessionId, updatedAt);
290+
private deleteChat(sessionId: string) {
291+
// Remove from storage
292+
this.storage.removeChat(sessionId);
252293

253-
// Remove from local state using both sessionId and updatedAt
254-
const index = this.chats.findIndex(
255-
(c) => c.sessionId === sessionId && c.updatedAt === updatedAt
256-
);
294+
// Remove from local state
295+
const index = this.chats.findIndex((c) => c.sessionId === sessionId);
257296
if (index !== -1) {
258-
const wasActive =
259-
this.activeChat?.sessionId === sessionId &&
260-
this.activeChat?.updatedAt === updatedAt;
297+
const wasActive = this.activeChat?.sessionId === sessionId;
261298
this.chats.splice(index, 1);
262299
if (wasActive) {
263300
this.activeChat = null;
@@ -277,7 +314,7 @@ class HistoryPanel {
277314
}
278315

279316
// Notify parent
280-
this.onChatItemDelete?.(sessionId, updatedAt);
317+
this.onChatItemDelete?.(sessionId);
281318
}
282319
}
283320

0 commit comments

Comments
 (0)