@hef2024/llmasaservice-ui 0.19.0 → 0.20.0

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.
@@ -332,7 +332,7 @@
332
332
  display: flex;
333
333
  gap: 4px;
334
334
  margin-top: 8px;
335
- opacity: 0;
335
+ opacity: 1;
336
336
  transition: opacity 0.2s;
337
337
  }
338
338
 
@@ -340,6 +340,31 @@
340
340
  opacity: 1;
341
341
  }
342
342
 
343
+ .ai-chat-action-button {
344
+ display: inline-flex;
345
+ align-items: center;
346
+ justify-content: center;
347
+ width: 32px;
348
+ height: 32px;
349
+ padding: 0;
350
+ background: transparent;
351
+ border: none;
352
+ border-radius: 6px;
353
+ color: var(--ai-sidebar-text-muted, #9ca3af);
354
+ cursor: pointer;
355
+ transition: background-color 0.15s, color 0.15s;
356
+ }
357
+
358
+ .ai-chat-action-button:hover {
359
+ background-color: var(--ai-button-ghost-hover, rgba(0, 0, 0, 0.04));
360
+ color: var(--ai-chat-assistant-text, #374151);
361
+ }
362
+
363
+ .dark-theme .ai-chat-action-button:hover {
364
+ background-color: var(--ai-button-ghost-hover, rgba(255, 255, 255, 0.08));
365
+ color: var(--ai-chat-assistant-text, #e5e7eb);
366
+ }
367
+
343
368
  /* ============================================================================
344
369
  Thinking Blocks
345
370
  ============================================================================ */
@@ -1616,3 +1641,97 @@
1616
1641
  }
1617
1642
  }
1618
1643
 
1644
+ /* ============================================================================
1645
+ Error Banner
1646
+ ============================================================================ */
1647
+
1648
+ .ai-chat-error-banner {
1649
+ display: flex;
1650
+ align-items: flex-start;
1651
+ gap: 12px;
1652
+ padding: 12px 16px;
1653
+ margin: 12px 16px;
1654
+ background: #fef2f2;
1655
+ border: 1px solid #fecaca;
1656
+ border-radius: 8px;
1657
+ animation: slideDown 0.2s ease-out;
1658
+ }
1659
+
1660
+ .dark-theme .ai-chat-error-banner {
1661
+ background: #7f1d1d;
1662
+ border-color: #991b1b;
1663
+ }
1664
+
1665
+ .ai-chat-error-banner__icon {
1666
+ flex-shrink: 0;
1667
+ color: #dc2626;
1668
+ margin-top: 2px;
1669
+ }
1670
+
1671
+ .dark-theme .ai-chat-error-banner__icon {
1672
+ color: #fca5a5;
1673
+ }
1674
+
1675
+ .ai-chat-error-banner__content {
1676
+ flex: 1;
1677
+ display: flex;
1678
+ flex-direction: column;
1679
+ gap: 4px;
1680
+ }
1681
+
1682
+ .ai-chat-error-banner__message {
1683
+ color: #991b1b;
1684
+ font-size: 14px;
1685
+ font-weight: 500;
1686
+ line-height: 1.4;
1687
+ }
1688
+
1689
+ .dark-theme .ai-chat-error-banner__message {
1690
+ color: #fecaca;
1691
+ }
1692
+
1693
+ .ai-chat-error-banner__hint {
1694
+ color: #b91c1c;
1695
+ font-size: 13px;
1696
+ line-height: 1.4;
1697
+ }
1698
+
1699
+ .dark-theme .ai-chat-error-banner__hint {
1700
+ color: #fca5a5;
1701
+ opacity: 0.8;
1702
+ }
1703
+
1704
+ .ai-chat-error-banner__close {
1705
+ flex-shrink: 0;
1706
+ background: none;
1707
+ border: none;
1708
+ padding: 4px;
1709
+ cursor: pointer;
1710
+ color: #991b1b;
1711
+ border-radius: 4px;
1712
+ transition: background-color 0.15s;
1713
+ }
1714
+
1715
+ .dark-theme .ai-chat-error-banner__close {
1716
+ color: #fca5a5;
1717
+ }
1718
+
1719
+ .ai-chat-error-banner__close:hover {
1720
+ background: rgba(220, 38, 38, 0.1);
1721
+ }
1722
+
1723
+ .dark-theme .ai-chat-error-banner__close:hover {
1724
+ background: rgba(252, 165, 165, 0.1);
1725
+ }
1726
+
1727
+ @keyframes slideDown {
1728
+ from {
1729
+ opacity: 0;
1730
+ transform: translateY(-8px);
1731
+ }
1732
+ to {
1733
+ opacity: 1;
1734
+ transform: translateY(0);
1735
+ }
1736
+ }
1737
+
@@ -235,6 +235,21 @@ const LLMAsAServiceLogo = () => (
235
235
  </svg>
236
236
  );
237
237
 
238
+ const AlertCircleIcon = () => (
239
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="ai-chat-icon-sm">
240
+ <circle cx="12" cy="12" r="10" />
241
+ <line x1="12" x2="12" y1="8" y2="12" />
242
+ <line x1="12" x2="12.01" y1="16" y2="16" />
243
+ </svg>
244
+ );
245
+
246
+ const CloseIcon = () => (
247
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="ai-chat-icon-sm">
248
+ <line x1="18" x2="6" y1="6" y2="18" />
249
+ <line x1="6" x2="18" y1="6" y2="18" />
250
+ </svg>
251
+ );
252
+
238
253
  // ============================================================================
239
254
  // Isolated Input Component - Prevents full re-renders on every keystroke
240
255
  // ============================================================================
@@ -685,6 +700,8 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
685
700
  const [newConversationConfirm, setNewConversationConfirm] = useState(false);
686
701
  const [justReset, setJustReset] = useState(false);
687
702
  const [copiedCallId, setCopiedCallId] = useState<string | null>(null);
703
+ const [feedbackCallId, setFeedbackCallId] = useState<{ callId: string; type: 'up' | 'down' } | null>(null);
704
+ const [error, setError] = useState<{ message: string; code?: string } | null>(null);
688
705
 
689
706
  // Refs
690
707
  const bottomRef = useRef<HTMLDivElement | null>(null);
@@ -752,6 +769,7 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
752
769
  lastCallId,
753
770
  stop,
754
771
  setResponse,
772
+ error: llmError,
755
773
  } = llmResult;
756
774
 
757
775
  // Tool-related properties (may not exist on all versions of useLLM)
@@ -989,6 +1007,50 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
989
1007
  return displayPrompt;
990
1008
  }, [hideRagContextInPrompt]);
991
1009
 
1010
+ // Built-in interaction tracking - reports to LLMAsAService API
1011
+ const interactionClicked = useCallback(async (
1012
+ callId: string,
1013
+ action: string,
1014
+ emailaddress: string = "",
1015
+ comment: string = ""
1016
+ ) => {
1017
+ console.log(`[AIChatPanel] Interaction: ${action} for callId: ${callId}`);
1018
+
1019
+ // Ensure conversation exists
1020
+ const convId = currentConversation || await ensureConversation();
1021
+
1022
+ // Use the callId parameter or fallback to conversation ID
1023
+ const finalCallId = callId || convId;
1024
+
1025
+ // Get email from customer data
1026
+ const isEmailAddress = (str: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(str);
1027
+ const email = emailaddress && emailaddress !== ""
1028
+ ? emailaddress
1029
+ : isEmailAddress(customer?.customer_user_email ?? "")
1030
+ ? customer?.customer_user_email
1031
+ : isEmailAddress(customer?.customer_id ?? "")
1032
+ ? customer?.customer_id
1033
+ : "";
1034
+
1035
+ // Send feedback to API
1036
+ try {
1037
+ await fetch(`${publicAPIUrl}/feedback/${finalCallId}/${action}`, {
1038
+ method: "POST",
1039
+ headers: {
1040
+ "Content-Type": "application/json",
1041
+ },
1042
+ body: JSON.stringify({
1043
+ project_id: project_id ?? "",
1044
+ conversation_id: convId ?? "",
1045
+ email: email,
1046
+ comment: comment,
1047
+ }),
1048
+ });
1049
+ } catch (err) {
1050
+ console.error('[AIChatPanel] Failed to send feedback:', err);
1051
+ }
1052
+ }, [currentConversation, ensureConversation, customer, project_id, publicAPIUrl]);
1053
+
992
1054
  // Copy to clipboard
993
1055
  const copyToClipboard = useCallback(async (text: string, callId: string) => {
994
1056
  try {
@@ -1003,10 +1065,46 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
1003
1065
  await navigator.clipboard.writeText(cleanText);
1004
1066
  setCopiedCallId(callId);
1005
1067
  setTimeout(() => setCopiedCallId(null), 2000);
1068
+
1069
+ // Report to API (built-in)
1070
+ await interactionClicked(callId, "copy");
1006
1071
  } catch (err) {
1007
1072
  console.error('Failed to copy:', err);
1008
1073
  }
1009
- }, []);
1074
+ }, [interactionClicked]);
1075
+
1076
+ // Handle thumbs up/down with visual feedback
1077
+ const handleThumbsUp = useCallback(async (callId: string) => {
1078
+ console.log('[AIChatPanel] Thumbs up clicked:', callId);
1079
+
1080
+ // Show visual feedback
1081
+ setFeedbackCallId({ callId, type: 'up' });
1082
+ setTimeout(() => setFeedbackCallId(null), 2000);
1083
+
1084
+ // Report to API (built-in)
1085
+ await interactionClicked(callId, "thumbsup");
1086
+
1087
+ // Call external callback if provided
1088
+ if (thumbsUpClick) {
1089
+ thumbsUpClick(callId);
1090
+ }
1091
+ }, [thumbsUpClick, interactionClicked]);
1092
+
1093
+ const handleThumbsDown = useCallback(async (callId: string) => {
1094
+ console.log('[AIChatPanel] Thumbs down clicked:', callId);
1095
+
1096
+ // Show visual feedback
1097
+ setFeedbackCallId({ callId, type: 'down' });
1098
+ setTimeout(() => setFeedbackCallId(null), 2000);
1099
+
1100
+ // Report to API (built-in)
1101
+ await interactionClicked(callId, "thumbsdown");
1102
+
1103
+ // Call external callback if provided
1104
+ if (thumbsDownClick) {
1105
+ thumbsDownClick(callId);
1106
+ }
1107
+ }, [thumbsDownClick, interactionClicked]);
1010
1108
 
1011
1109
  // Scroll to bottom - throttled using RAF to prevent layout thrashing
1012
1110
  const scrollToBottom = useCallback(() => {
@@ -1039,9 +1137,17 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
1039
1137
  setThinkingBlocks([]);
1040
1138
  setCurrentThinkingIndex(0);
1041
1139
 
1140
+ // Clear any previous errors
1141
+ setError(null);
1142
+
1042
1143
  // Reset scroll tracking for new message - enable auto-scroll
1043
1144
  setUserHasScrolled(false);
1044
1145
 
1146
+ // IMPORTANT: Clear the response BEFORE setting new lastKey
1147
+ // This prevents the old response from being written to the new history entry
1148
+ // when the history update effect runs
1149
+ setResponse('');
1150
+
1045
1151
  // Handle stop if not idle (matches ChatPanel)
1046
1152
  if (!idle) {
1047
1153
  stop(lastController);
@@ -1142,7 +1248,48 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
1142
1248
  true, // includeHistory
1143
1249
  service, // group_id from agent config
1144
1250
  convId, // Use the conversation ID from ensureConversation
1145
- newController
1251
+ newController,
1252
+ undefined, // onComplete
1253
+ (errorMsg: string) => {
1254
+ // Error callback - handle errors immediately
1255
+ console.log('[AIChatPanel] Error callback triggered:', errorMsg);
1256
+
1257
+ // Detect 413 Content Too Large error
1258
+ if (errorMsg.includes('413') || errorMsg.toLowerCase().includes('content too large')) {
1259
+ setError({
1260
+ message: 'The context is too large to process. Please start a new conversation or reduce the amount of context.',
1261
+ code: '413',
1262
+ });
1263
+ }
1264
+ // Detect other network errors
1265
+ else if (errorMsg.toLowerCase().includes('network error') || errorMsg.toLowerCase().includes('fetch')) {
1266
+ setError({
1267
+ message: 'Network error. Please check your connection and try again.',
1268
+ code: 'NETWORK_ERROR',
1269
+ });
1270
+ }
1271
+ // Generic error
1272
+ else {
1273
+ setError({
1274
+ message: errorMsg,
1275
+ code: 'UNKNOWN_ERROR',
1276
+ });
1277
+ }
1278
+
1279
+ // Reset loading state
1280
+ setIsLoading(false);
1281
+
1282
+ // Update history to show error
1283
+ if (promptKey) {
1284
+ setHistory((prev) => ({
1285
+ ...prev,
1286
+ [promptKey]: {
1287
+ content: `Error: ${errorMsg}`,
1288
+ callId: lastCallId || '',
1289
+ },
1290
+ }));
1291
+ }
1292
+ }
1146
1293
  );
1147
1294
 
1148
1295
  setLastMessages(messagesAndHistory);
@@ -1174,6 +1321,7 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
1174
1321
  dataWithExtras,
1175
1322
  scrollToBottom,
1176
1323
  onConversationCreated,
1324
+ setResponse,
1177
1325
  ]);
1178
1326
 
1179
1327
  // Handle suggestion click - directly sends like ChatPanel does
@@ -1215,6 +1363,7 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
1215
1363
  setJustReset(true);
1216
1364
  setLastController(new AbortController());
1217
1365
  setUserHasScrolled(false);
1366
+ setError(null); // Clear any errors
1218
1367
 
1219
1368
  setTimeout(() => {
1220
1369
  setJustReset(false);
@@ -1265,6 +1414,9 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
1265
1414
  if (wasStreaming && isNowIdle && !hasNotifiedCompletionRef.current) {
1266
1415
  hasNotifiedCompletionRef.current = true;
1267
1416
 
1417
+ // Reset loading state on completion
1418
+ setIsLoading(false);
1419
+
1268
1420
  // Get the latest values from refs (stable, not from closure)
1269
1421
  const currentHistory = latestHistoryRef.current;
1270
1422
  const currentLastKey = lastKeyRef.current;
@@ -1396,6 +1548,52 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
1396
1548
  }
1397
1549
  }, [initialPrompt, continueChat]);
1398
1550
 
1551
+ // Monitor for errors from useLLM hook
1552
+ useEffect(() => {
1553
+ if (llmError && llmError.trim()) {
1554
+ console.log('[AIChatPanel] Error detected:', llmError);
1555
+
1556
+ // Parse error message to detect specific error types
1557
+ const errorMessage = llmError;
1558
+
1559
+ // Detect 413 Content Too Large error
1560
+ if (errorMessage.includes('413') || errorMessage.toLowerCase().includes('content too large')) {
1561
+ setError({
1562
+ message: 'The context is too large to process. Please start a new conversation or reduce the amount of context.',
1563
+ code: '413',
1564
+ });
1565
+ }
1566
+ // Detect other network errors
1567
+ else if (errorMessage.toLowerCase().includes('network error') || errorMessage.toLowerCase().includes('fetch')) {
1568
+ setError({
1569
+ message: 'Network error. Please check your connection and try again.',
1570
+ code: 'NETWORK_ERROR',
1571
+ });
1572
+ }
1573
+ // Generic error
1574
+ else {
1575
+ setError({
1576
+ message: errorMessage,
1577
+ code: 'UNKNOWN_ERROR',
1578
+ });
1579
+ }
1580
+
1581
+ // Reset loading state
1582
+ setIsLoading(false);
1583
+
1584
+ // Update history to show error
1585
+ if (lastKey) {
1586
+ setHistory((prev) => ({
1587
+ ...prev,
1588
+ [lastKey]: {
1589
+ content: `Error: ${errorMessage}`,
1590
+ callId: lastCallId || '',
1591
+ },
1592
+ }));
1593
+ }
1594
+ }
1595
+ }, [llmError, lastKey, lastCallId]);
1596
+
1399
1597
  // ============================================================================
1400
1598
  // Render Helpers
1401
1599
  // ============================================================================
@@ -1420,11 +1618,20 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
1420
1618
  }, [prismStyle]);
1421
1619
 
1422
1620
  // Agent suggestion card component for inline agent handoff
1423
- const AgentSuggestionCard = useCallback(({ agentId, agentName, reason }: {
1621
+ const AgentSuggestionCard = React.memo(({ agentId, agentName, reason }: {
1424
1622
  agentId: string;
1425
1623
  agentName: string;
1426
1624
  reason: string;
1427
1625
  }) => {
1626
+ // Auto-scroll when the agent suggestion card appears
1627
+ useEffect(() => {
1628
+ // Small delay to ensure the card is fully rendered in the DOM
1629
+ const timer = setTimeout(() => {
1630
+ scrollToBottom();
1631
+ }, 100);
1632
+ return () => clearTimeout(timer);
1633
+ }, []); // Empty deps - only run on mount
1634
+
1428
1635
  if (!agentId || !onAgentChange) return null;
1429
1636
 
1430
1637
  // Validate agent ID - must be a valid UUID or exist in agentOptions
@@ -1541,15 +1748,15 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
1541
1748
  onAgentChange(agentId);
1542
1749
  // Scroll to bottom after a brief delay to let React re-render
1543
1750
  setTimeout(() => {
1544
- bottomRef.current?.scrollIntoView({ behavior: 'auto' });
1545
- }, 50);
1751
+ scrollToBottom();
1752
+ }, 100);
1546
1753
  }}
1547
1754
  >
1548
1755
  Switch
1549
1756
  </Button>
1550
1757
  </span>
1551
1758
  );
1552
- }, [onAgentChange, agentOptions, currentAgentId]);
1759
+ });
1553
1760
 
1554
1761
  // Markdown components including custom agent-suggestion element
1555
1762
  const markdownComponents = useMemo(() => ({
@@ -1621,6 +1828,30 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
1621
1828
  {/* Title */}
1622
1829
  {title && <div className="ai-chat-panel__title">{title}</div>}
1623
1830
 
1831
+ {/* Error Banner */}
1832
+ {error && (
1833
+ <div className="ai-chat-error-banner">
1834
+ <div className="ai-chat-error-banner__icon">
1835
+ <AlertCircleIcon />
1836
+ </div>
1837
+ <div className="ai-chat-error-banner__content">
1838
+ <div className="ai-chat-error-banner__message">{error.message}</div>
1839
+ {error.code === '413' && (
1840
+ <div className="ai-chat-error-banner__hint">
1841
+ Try starting a new conversation or reducing the amount of information being sent.
1842
+ </div>
1843
+ )}
1844
+ </div>
1845
+ <button
1846
+ className="ai-chat-error-banner__close"
1847
+ onClick={() => setError(null)}
1848
+ aria-label="Dismiss error"
1849
+ >
1850
+ <CloseIcon />
1851
+ </button>
1852
+ </div>
1853
+ )}
1854
+
1624
1855
  {/* Messages Area */}
1625
1856
  <ScrollArea className="ai-chat-panel__messages" ref={responseAreaRef}>
1626
1857
  {/* Initial Message */}
@@ -1635,8 +1866,8 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
1635
1866
  )}
1636
1867
 
1637
1868
  {/* History */}
1638
- {Object.entries(history).map(([prompt, entry], index) => {
1639
- const isLastEntry = index === Object.keys(history).length - 1;
1869
+ {Object.entries(history).map(([prompt, entry], index, entries) => {
1870
+ const isLastEntry = index === entries.length - 1;
1640
1871
  // Check if this is a system message (injected by page context, etc.)
1641
1872
  const isSystemMessage = prompt.startsWith('__system__:');
1642
1873
  // Process thinking tags first, then apply actions at render time
@@ -1697,41 +1928,52 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
1697
1928
  </div>
1698
1929
 
1699
1930
  {/* Action Buttons */}
1700
- {idle && !isLoading && (
1931
+ {(!isLastEntry || !isLoading) && (
1701
1932
  <div className="ai-chat-message__actions">
1702
- <Tooltip content={copiedCallId === entry.callId ? 'Copied!' : 'Copy'}>
1703
- <Button
1704
- variant="ghost"
1705
- size="icon"
1706
- onClick={() => copyToClipboard(entry.content, entry.callId)}
1707
- >
1708
- <CopyIcon />
1709
- </Button>
1710
- </Tooltip>
1933
+ <button
1934
+ className="ai-chat-action-button"
1935
+ onClick={() => copyToClipboard(entry.content, entry.callId)}
1936
+ title={copiedCallId === entry.callId ? 'Copied!' : 'Copy'}
1937
+ >
1938
+ {copiedCallId === entry.callId ? (
1939
+ <span style={{ fontSize: '11px', fontWeight: 500 }}>Copied!</span>
1940
+ ) : (
1941
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="ai-chat-icon-sm">
1942
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
1943
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
1944
+ </svg>
1945
+ )}
1946
+ </button>
1711
1947
 
1712
- {thumbsUpClick && (
1713
- <Tooltip content="Good response">
1714
- <Button
1715
- variant="ghost"
1716
- size="icon"
1717
- onClick={() => thumbsUpClick(entry.callId)}
1718
- >
1719
- <ThumbsUpIcon />
1720
- </Button>
1721
- </Tooltip>
1722
- )}
1948
+ <button
1949
+ className="ai-chat-action-button"
1950
+ onClick={() => handleThumbsUp(entry.callId)}
1951
+ title={feedbackCallId?.callId === entry.callId && feedbackCallId?.type === 'up' ? 'Thanks!' : 'Good response'}
1952
+ style={feedbackCallId?.callId === entry.callId && feedbackCallId?.type === 'up' ? { color: '#22c55e' } : undefined}
1953
+ >
1954
+ {feedbackCallId?.callId === entry.callId && feedbackCallId?.type === 'up' ? (
1955
+ <span style={{ fontSize: '11px', fontWeight: 500 }}>Thanks!</span>
1956
+ ) : (
1957
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="ai-chat-icon-sm">
1958
+ <path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3" />
1959
+ </svg>
1960
+ )}
1961
+ </button>
1723
1962
 
1724
- {thumbsDownClick && (
1725
- <Tooltip content="Poor response">
1726
- <Button
1727
- variant="ghost"
1728
- size="icon"
1729
- onClick={() => thumbsDownClick(entry.callId)}
1730
- >
1731
- <ThumbsDownIcon />
1732
- </Button>
1733
- </Tooltip>
1734
- )}
1963
+ <button
1964
+ className="ai-chat-action-button"
1965
+ onClick={() => handleThumbsDown(entry.callId)}
1966
+ title={feedbackCallId?.callId === entry.callId && feedbackCallId?.type === 'down' ? 'Thanks!' : 'Poor response'}
1967
+ style={feedbackCallId?.callId === entry.callId && feedbackCallId?.type === 'down' ? { color: '#ef4444' } : undefined}
1968
+ >
1969
+ {feedbackCallId?.callId === entry.callId && feedbackCallId?.type === 'down' ? (
1970
+ <span style={{ fontSize: '11px', fontWeight: 500 }}>Thanks!</span>
1971
+ ) : (
1972
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="ai-chat-icon-sm">
1973
+ <path d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17" />
1974
+ </svg>
1975
+ )}
1976
+ </button>
1735
1977
  </div>
1736
1978
  )}
1737
1979
  </div>