@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.
- 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 +242 -56
- package/dist/index.mjs +242 -56
- 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 +282 -40
- package/src/ChatPanel.css +111 -0
- package/src/ChatPanel.tsx +105 -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,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 =
|
|
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
|
-
|
|
1545
|
-
},
|
|
1751
|
+
scrollToBottom();
|
|
1752
|
+
}, 100);
|
|
1546
1753
|
}}
|
|
1547
1754
|
>
|
|
1548
1755
|
Switch
|
|
1549
1756
|
</Button>
|
|
1550
1757
|
</span>
|
|
1551
1758
|
);
|
|
1552
|
-
}
|
|
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 ===
|
|
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
|
-
{
|
|
1931
|
+
{(!isLastEntry || !isLoading) && (
|
|
1701
1932
|
<div className="ai-chat-message__actions">
|
|
1702
|
-
<
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
<
|
|
1709
|
-
|
|
1710
|
-
|
|
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
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
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
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
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>
|