@ihoomanai/chat-widget 3.0.19 → 3.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/widget.ts CHANGED
@@ -17,7 +17,7 @@ import type {
17
17
  WidgetView,
18
18
  } from './types';
19
19
 
20
- const VERSION = '3.0.13';
20
+ const VERSION = '3.0.21';
21
21
  const STORAGE_PREFIX = 'ihooman_chat_';
22
22
  const DEFAULT_SERVER_URL = 'https://api.ihooman.ai';
23
23
 
@@ -91,6 +91,7 @@ let state: WidgetState = {
91
91
 
92
92
  let currentView: WidgetView = 'chat';
93
93
  let isLiveAgentMode = false;
94
+ let isConversationClosed = false;
94
95
  let shownProactiveIds: string[] = [];
95
96
  let proactiveCooldowns: Record<string, number> = {};
96
97
  let proactiveCheckInterval: ReturnType<typeof setInterval> | null = null;
@@ -334,6 +335,8 @@ function generateStyles(): string {
334
335
  .ihooman-input-btn.send:hover { opacity: 0.9; }
335
336
  .ihooman-input-btn.send:disabled { opacity: 0.5; cursor: not-allowed; }
336
337
  .ihooman-input-btn svg { width: 16px; height: 16px; }
338
+ .ihooman-input:disabled { opacity: 0.6; cursor: not-allowed; }
339
+ .ihooman-input-btn.attach:disabled { opacity: 0.4; cursor: not-allowed; }
337
340
  .ihooman-file-input { display: none; }
338
341
  .ihooman-powered { text-align: center; padding: 8px; font-size: 11px; color: ${mutedColor}; background: ${bgColor}; }
339
342
  .ihooman-powered a { color: ${primaryColor}; text-decoration: none; }
@@ -684,23 +687,11 @@ function addMessage(content: string, sender: 'user' | 'bot' = 'bot', metadata: M
684
687
  `<button class="ihooman-quick-reply" data-text="${escapeHtml(qr.text)}">${escapeHtml(qr.text)}</button>`
685
688
  ).join('')}</div>`;
686
689
  }
687
-
688
- // Feedback buttons for bot messages
689
- let feedbackHtml = '';
690
- if (sender === 'bot' && !metadata?.is_system_message) {
691
- feedbackHtml = `
692
- <div class="ihooman-feedback-btns">
693
- <button class="ihooman-feedback-btn" data-feedback="up" title="Helpful">${icons.thumbUp}</button>
694
- <button class="ihooman-feedback-btn" data-feedback="down" title="Not helpful">${icons.thumbDown}</button>
695
- </div>
696
- `;
697
- }
698
690
 
699
691
  el.innerHTML = `
700
692
  <div class="ihooman-message-content">${parseMarkdown(content)}</div>
701
693
  ${escalationButtonsHtml}
702
694
  ${quickRepliesHtml}
703
- ${feedbackHtml}
704
695
  ${config.showTimestamps ? `<div class="ihooman-message-time">${formatTime(message.timestamp)}</div>` : ''}
705
696
  `;
706
697
 
@@ -723,16 +714,6 @@ function addMessage(content: string, sender: 'user' | 'bot' = 'bot', metadata: M
723
714
  });
724
715
  });
725
716
 
726
- // Feedback listeners
727
- el.querySelectorAll('.ihooman-feedback-btn').forEach(btn => {
728
- btn.addEventListener('click', () => {
729
- const feedback = (btn as HTMLElement).dataset.feedback;
730
- el.querySelectorAll('.ihooman-feedback-btn').forEach(b => b.classList.remove('active'));
731
- btn.classList.add('active');
732
- submitFeedback(message.id, feedback === 'up' ? 'positive' : 'negative');
733
- });
734
- });
735
-
736
717
  const typing = elements.messages.querySelector('.ihooman-typing');
737
718
  if (typing) typing.remove();
738
719
 
@@ -1049,22 +1030,6 @@ function stopLiveAgentPolling(): void {
1049
1030
  }
1050
1031
  }
1051
1032
 
1052
- async function submitFeedback(messageId: string, feedbackType: 'positive' | 'negative'): Promise<void> {
1053
- try {
1054
- await fetch(`${config.serverUrl}/api/v1/feedback/submit?widget_id=${encodeURIComponent(config.widgetId)}`, {
1055
- method: 'POST',
1056
- headers: { 'Content-Type': 'application/json' },
1057
- body: JSON.stringify({
1058
- message_id: messageId,
1059
- session_id: state.sessionId,
1060
- feedback_type: feedbackType,
1061
- }),
1062
- });
1063
- } catch (error) {
1064
- console.error('Error submitting feedback:', error);
1065
- }
1066
- }
1067
-
1068
1033
  async function handleSurveySubmit(): Promise<void> {
1069
1034
  const starsContainer = elements.surveyView?.querySelector('.ihooman-survey-stars');
1070
1035
  const activeStars = starsContainer?.querySelectorAll('.ihooman-survey-star.active');
@@ -1081,10 +1046,11 @@ async function handleSurveySubmit(): Promise<void> {
1081
1046
  method: 'POST',
1082
1047
  headers: { 'Content-Type': 'application/json' },
1083
1048
  body: JSON.stringify({
1084
- survey_id: config.surveyConfig?.id,
1049
+ survey_config_id: config.surveyConfig?.id,
1085
1050
  session_id: state.sessionId,
1051
+ visitor_id: state.visitorId,
1086
1052
  rating,
1087
- comment,
1053
+ comment: comment || null,
1088
1054
  }),
1089
1055
  });
1090
1056
 
@@ -1170,7 +1136,26 @@ function hideTyping(): void {
1170
1136
  if (typing) typing.remove();
1171
1137
  }
1172
1138
 
1139
+ function disableInput(): void {
1140
+ if (elements.input) {
1141
+ elements.input.disabled = true;
1142
+ elements.input.placeholder = 'Conversation closed — start a new chat to continue';
1143
+ }
1144
+ if (elements.sendBtn) elements.sendBtn.disabled = true;
1145
+ if (elements.attachBtn) elements.attachBtn.disabled = true;
1146
+ }
1147
+
1148
+ function enableInput(): void {
1149
+ if (elements.input) {
1150
+ elements.input.disabled = false;
1151
+ elements.input.placeholder = config.placeholder || 'Type a message...';
1152
+ }
1153
+ if (elements.sendBtn) elements.sendBtn.disabled = !elements.input?.value.trim();
1154
+ if (elements.attachBtn) elements.attachBtn.disabled = false;
1155
+ }
1156
+
1173
1157
  function handleSendClick(): void {
1158
+ if (isConversationClosed) return;
1174
1159
  const content = elements.input?.value.trim();
1175
1160
  if (!content) return;
1176
1161
 
@@ -1200,6 +1185,7 @@ function hidePresetQuestions(): void {
1200
1185
  }
1201
1186
 
1202
1187
  async function sendMessageToServer(content: string): Promise<void> {
1188
+ if (isConversationClosed) return;
1203
1189
  if (ws && ws.readyState === WebSocket.OPEN) {
1204
1190
  ws.send(JSON.stringify({ type: 'message', content }));
1205
1191
  return;
@@ -1543,10 +1529,12 @@ function connectWebSocket(chatEndpoint?: string): void {
1543
1529
  emit('escalation:accepted', { type: 'live_agent' });
1544
1530
  }
1545
1531
  if (metadata.closed_by_agent) {
1546
- // Agent has closed the conversation - return to AI mode
1532
+ // Agent has closed the conversation - disable input, require new conversation
1547
1533
  isLiveAgentMode = false;
1534
+ isConversationClosed = true;
1548
1535
  stopLiveAgentPolling();
1549
1536
  updateStatusBar('hidden');
1537
+ disableInput();
1550
1538
  emit('escalation:end', { type: 'live_agent' });
1551
1539
  }
1552
1540
 
@@ -1578,8 +1566,10 @@ function connectWebSocket(chatEndpoint?: string): void {
1578
1566
  emit('escalation:accepted', { type: 'live_agent' });
1579
1567
  } else if (data.status === 'disconnected') {
1580
1568
  isLiveAgentMode = false;
1569
+ isConversationClosed = true;
1581
1570
  stopLiveAgentPolling();
1582
1571
  updateStatusBar('hidden');
1572
+ disableInput();
1583
1573
  emit('escalation:end', { type: 'live_agent' });
1584
1574
  }
1585
1575
  } else if (data.type === 'error') {
@@ -1700,6 +1690,7 @@ function startNewConversation(): void {
1700
1690
  state.visitorId = null;
1701
1691
  state.messages = [];
1702
1692
  isLiveAgentMode = false;
1693
+ isConversationClosed = false;
1703
1694
 
1704
1695
  storage('session_id', null);
1705
1696
  state.visitorId = generateId('v_');
@@ -1709,6 +1700,7 @@ function startNewConversation(): void {
1709
1700
 
1710
1701
  updateStatusBar('hidden');
1711
1702
  stopLiveAgentPolling();
1703
+ enableInput();
1712
1704
 
1713
1705
  reconnectAttempts = 0;
1714
1706
 
@@ -1761,7 +1753,7 @@ function destroy(): void {
1761
1753
  }
1762
1754
 
1763
1755
  function sendMessage(content: string): void {
1764
- if (!content.trim()) return;
1756
+ if (!content.trim() || isConversationClosed) return;
1765
1757
  if (elements.input) elements.input.value = content;
1766
1758
  handleSendClick();
1767
1759
  }