@hef2024/llmasaservice-ui 0.19.1 → 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.
- package/AICHATPANEL-PORT-INVENTORY.md +830 -0
- package/DEBUG-ERROR-HANDLING.md +130 -0
- package/FIX-APPLIED.md +234 -0
- package/IMPLEMENTATION-COMPLETE.md +246 -0
- package/README.md +1 -0
- package/dist/index.css +190 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +237 -54
- package/dist/index.mjs +237 -54
- package/docs/CHANGELOG-ERROR-HANDLING.md +247 -0
- package/docs/ERROR-HANDLING-413.md +253 -0
- package/docs/ERROR-HANDLING-SUMMARY.md +131 -0
- package/package.json +1 -1
- package/src/AIChatPanel.css +120 -1
- package/src/AIChatPanel.tsx +274 -38
- package/src/ChatPanel.css +111 -0
- package/src/ChatPanel.tsx +100 -4
package/src/AIChatPanel.css
CHANGED
|
@@ -332,7 +332,7 @@
|
|
|
332
332
|
display: flex;
|
|
333
333
|
gap: 4px;
|
|
334
334
|
margin-top: 8px;
|
|
335
|
-
opacity:
|
|
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
|
+
|
package/src/AIChatPanel.tsx
CHANGED
|
@@ -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,6 +1137,9 @@ 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
|
|
|
@@ -1147,7 +1248,48 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
|
|
|
1147
1248
|
true, // includeHistory
|
|
1148
1249
|
service, // group_id from agent config
|
|
1149
1250
|
convId, // Use the conversation ID from ensureConversation
|
|
1150
|
-
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
|
+
}
|
|
1151
1293
|
);
|
|
1152
1294
|
|
|
1153
1295
|
setLastMessages(messagesAndHistory);
|
|
@@ -1221,6 +1363,7 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
|
|
|
1221
1363
|
setJustReset(true);
|
|
1222
1364
|
setLastController(new AbortController());
|
|
1223
1365
|
setUserHasScrolled(false);
|
|
1366
|
+
setError(null); // Clear any errors
|
|
1224
1367
|
|
|
1225
1368
|
setTimeout(() => {
|
|
1226
1369
|
setJustReset(false);
|
|
@@ -1271,6 +1414,9 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
|
|
|
1271
1414
|
if (wasStreaming && isNowIdle && !hasNotifiedCompletionRef.current) {
|
|
1272
1415
|
hasNotifiedCompletionRef.current = true;
|
|
1273
1416
|
|
|
1417
|
+
// Reset loading state on completion
|
|
1418
|
+
setIsLoading(false);
|
|
1419
|
+
|
|
1274
1420
|
// Get the latest values from refs (stable, not from closure)
|
|
1275
1421
|
const currentHistory = latestHistoryRef.current;
|
|
1276
1422
|
const currentLastKey = lastKeyRef.current;
|
|
@@ -1402,6 +1548,52 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
|
|
|
1402
1548
|
}
|
|
1403
1549
|
}, [initialPrompt, continueChat]);
|
|
1404
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
|
+
|
|
1405
1597
|
// ============================================================================
|
|
1406
1598
|
// Render Helpers
|
|
1407
1599
|
// ============================================================================
|
|
@@ -1426,11 +1618,20 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
|
|
|
1426
1618
|
}, [prismStyle]);
|
|
1427
1619
|
|
|
1428
1620
|
// Agent suggestion card component for inline agent handoff
|
|
1429
|
-
const AgentSuggestionCard =
|
|
1621
|
+
const AgentSuggestionCard = React.memo(({ agentId, agentName, reason }: {
|
|
1430
1622
|
agentId: string;
|
|
1431
1623
|
agentName: string;
|
|
1432
1624
|
reason: string;
|
|
1433
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
|
+
|
|
1434
1635
|
if (!agentId || !onAgentChange) return null;
|
|
1435
1636
|
|
|
1436
1637
|
// Validate agent ID - must be a valid UUID or exist in agentOptions
|
|
@@ -1547,15 +1748,15 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
|
|
|
1547
1748
|
onAgentChange(agentId);
|
|
1548
1749
|
// Scroll to bottom after a brief delay to let React re-render
|
|
1549
1750
|
setTimeout(() => {
|
|
1550
|
-
|
|
1551
|
-
},
|
|
1751
|
+
scrollToBottom();
|
|
1752
|
+
}, 100);
|
|
1552
1753
|
}}
|
|
1553
1754
|
>
|
|
1554
1755
|
Switch
|
|
1555
1756
|
</Button>
|
|
1556
1757
|
</span>
|
|
1557
1758
|
);
|
|
1558
|
-
}
|
|
1759
|
+
});
|
|
1559
1760
|
|
|
1560
1761
|
// Markdown components including custom agent-suggestion element
|
|
1561
1762
|
const markdownComponents = useMemo(() => ({
|
|
@@ -1627,6 +1828,30 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
|
|
|
1627
1828
|
{/* Title */}
|
|
1628
1829
|
{title && <div className="ai-chat-panel__title">{title}</div>}
|
|
1629
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
|
+
|
|
1630
1855
|
{/* Messages Area */}
|
|
1631
1856
|
<ScrollArea className="ai-chat-panel__messages" ref={responseAreaRef}>
|
|
1632
1857
|
{/* Initial Message */}
|
|
@@ -1703,41 +1928,52 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
|
|
|
1703
1928
|
</div>
|
|
1704
1929
|
|
|
1705
1930
|
{/* Action Buttons */}
|
|
1706
|
-
{
|
|
1931
|
+
{(!isLastEntry || !isLoading) && (
|
|
1707
1932
|
<div className="ai-chat-message__actions">
|
|
1708
|
-
<
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
<
|
|
1715
|
-
|
|
1716
|
-
|
|
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>
|
|
1717
1947
|
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
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>
|
|
1729
1962
|
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
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>
|
|
1741
1977
|
</div>
|
|
1742
1978
|
)}
|
|
1743
1979
|
</div>
|
package/src/ChatPanel.css
CHANGED
|
@@ -1090,4 +1090,115 @@ button[data-pending="true"]::after {
|
|
|
1090
1090
|
|
|
1091
1091
|
.searching-section {
|
|
1092
1092
|
border-left: 3px solid #7b68ee;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
/* ============================================================================
|
|
1096
|
+
Error Banner
|
|
1097
|
+
============================================================================ */
|
|
1098
|
+
|
|
1099
|
+
.error-banner {
|
|
1100
|
+
display: flex;
|
|
1101
|
+
align-items: flex-start;
|
|
1102
|
+
gap: 12px;
|
|
1103
|
+
padding: 12px 16px;
|
|
1104
|
+
margin: 12px 16px;
|
|
1105
|
+
background: #fef2f2;
|
|
1106
|
+
border: 1px solid #fecaca;
|
|
1107
|
+
border-radius: 8px;
|
|
1108
|
+
animation: slideDown 0.2s ease-out;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
.dark-theme .error-banner {
|
|
1112
|
+
background: #7f1d1d;
|
|
1113
|
+
border-color: #991b1b;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
.error-banner__icon {
|
|
1117
|
+
flex-shrink: 0;
|
|
1118
|
+
color: #dc2626;
|
|
1119
|
+
margin-top: 2px;
|
|
1120
|
+
width: 20px;
|
|
1121
|
+
height: 20px;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
.error-banner__icon svg {
|
|
1125
|
+
width: 100%;
|
|
1126
|
+
height: 100%;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
.dark-theme .error-banner__icon {
|
|
1130
|
+
color: #fca5a5;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
.error-banner__content {
|
|
1134
|
+
flex: 1;
|
|
1135
|
+
display: flex;
|
|
1136
|
+
flex-direction: column;
|
|
1137
|
+
gap: 4px;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
.error-banner__message {
|
|
1141
|
+
color: #991b1b;
|
|
1142
|
+
font-size: 14px;
|
|
1143
|
+
font-weight: 500;
|
|
1144
|
+
line-height: 1.4;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
.dark-theme .error-banner__message {
|
|
1148
|
+
color: #fecaca;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
.error-banner__hint {
|
|
1152
|
+
color: #b91c1c;
|
|
1153
|
+
font-size: 13px;
|
|
1154
|
+
line-height: 1.4;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
.dark-theme .error-banner__hint {
|
|
1158
|
+
color: #fca5a5;
|
|
1159
|
+
opacity: 0.8;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
.error-banner__close {
|
|
1163
|
+
flex-shrink: 0;
|
|
1164
|
+
background: none;
|
|
1165
|
+
border: none;
|
|
1166
|
+
padding: 4px;
|
|
1167
|
+
cursor: pointer;
|
|
1168
|
+
color: #991b1b;
|
|
1169
|
+
border-radius: 4px;
|
|
1170
|
+
transition: background-color 0.15s;
|
|
1171
|
+
width: 24px;
|
|
1172
|
+
height: 24px;
|
|
1173
|
+
display: flex;
|
|
1174
|
+
align-items: center;
|
|
1175
|
+
justify-content: center;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
.error-banner__close svg {
|
|
1179
|
+
width: 16px;
|
|
1180
|
+
height: 16px;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
.dark-theme .error-banner__close {
|
|
1184
|
+
color: #fca5a5;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
.error-banner__close:hover {
|
|
1188
|
+
background: rgba(220, 38, 38, 0.1);
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
.dark-theme .error-banner__close:hover {
|
|
1192
|
+
background: rgba(252, 165, 165, 0.1);
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
@keyframes slideDown {
|
|
1196
|
+
from {
|
|
1197
|
+
opacity: 0;
|
|
1198
|
+
transform: translateY(-8px);
|
|
1199
|
+
}
|
|
1200
|
+
to {
|
|
1201
|
+
opacity: 1;
|
|
1202
|
+
transform: translateY(0);
|
|
1203
|
+
}
|
|
1093
1204
|
}
|