@hef2024/llmasaservice-ui 0.19.1 → 0.20.1

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.
@@ -159,7 +159,9 @@ const AgentPanel: React.FC<AgentPanelProps & ExtraProps> = ({
159
159
  useEffect(() => {
160
160
  const fetchAgentData = async () => {
161
161
  try {
162
- const fetchUrl = `https://api.llmasaservice.io/agents/${agent}`;
162
+ const fetchUrl = url.endsWith("dev")
163
+ ? `https://8ftw8droff.execute-api.us-east-1.amazonaws.com/dev/agents/${agent}`
164
+ : `https://api.llmasaservice.io/agents/${agent}`;
163
165
 
164
166
  const response = await fetch(fetchUrl, {
165
167
  method: "GET",
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
  }
package/src/ChatPanel.tsx CHANGED
@@ -222,6 +222,9 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
222
222
  >([]);
223
223
  const [currentThinkingIndex, setCurrentThinkingIndex] = useState(0);
224
224
 
225
+ // State for error handling
226
+ const [error, setError] = useState<{ message: string; code?: string } | null>(null);
227
+
225
228
  // State for tracking user-resized textarea height
226
229
  const [userResizedHeight, setUserResizedHeight] = useState<number | null>(null);
227
230
 
@@ -680,7 +683,7 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
680
683
  fetchAndSetTools();
681
684
  }, [mcpServers, publicAPIUrl]);
682
685
 
683
- const { send, response, idle, stop, lastCallId, setResponse } = useLLM({
686
+ const llmResult = useLLM({
684
687
  project_id: project_id,
685
688
  customer: currentCustomer,
686
689
  url: url,
@@ -694,6 +697,57 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
694
697
  };
695
698
  }) as [],
696
699
  });
700
+
701
+ const { send, response, idle, stop, lastCallId, setResponse, error: llmError } = llmResult;
702
+
703
+ // Error handler function - reusable for both error state and callbacks
704
+ const handleError = useCallback((errorMessage: string, historyKey?: string) => {
705
+ console.log('[ChatPanel] Error detected:', errorMessage);
706
+
707
+ // Detect 413 Content Too Large error
708
+ if (errorMessage.includes('413') || errorMessage.toLowerCase().includes('content too large')) {
709
+ setError({
710
+ message: 'The context is too large to process. Please start a new conversation or reduce the amount of context.',
711
+ code: '413',
712
+ });
713
+ }
714
+ // Detect other network errors
715
+ else if (errorMessage.toLowerCase().includes('network error') || errorMessage.toLowerCase().includes('fetch')) {
716
+ setError({
717
+ message: 'Network error. Please check your connection and try again.',
718
+ code: 'NETWORK_ERROR',
719
+ });
720
+ }
721
+ // Generic error
722
+ else {
723
+ setError({
724
+ message: errorMessage,
725
+ code: 'UNKNOWN_ERROR',
726
+ });
727
+ }
728
+
729
+ // Reset loading state
730
+ setIsLoading(false);
731
+
732
+ // Update history to show error
733
+ const keyToUse = historyKey || lastKey;
734
+ if (keyToUse) {
735
+ setHistory((prev) => ({
736
+ ...prev,
737
+ [keyToUse]: {
738
+ content: `Error: ${errorMessage}`,
739
+ callId: lastCallId || '',
740
+ },
741
+ }));
742
+ }
743
+ }, [lastKey, lastCallId]);
744
+
745
+ // Monitor for errors from useLLM hook (for errors not caught by callback)
746
+ useEffect(() => {
747
+ if (llmError && llmError.trim()) {
748
+ handleError(llmError);
749
+ }
750
+ }, [llmError, handleError]);
697
751
 
698
752
  // Centralized action processing function to eliminate duplication
699
753
  const processActionsOnContent = useCallback((
@@ -1459,7 +1513,9 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
1459
1513
  true,
1460
1514
  service,
1461
1515
  currentConversation,
1462
- lastController
1516
+ lastController,
1517
+ undefined, // onComplete
1518
+ (errorMsg: string) => handleError(errorMsg) // onError
1463
1519
  );
1464
1520
  } catch (error) {
1465
1521
  console.error("Error in processing all tools:", error);
@@ -1941,7 +1997,9 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
1941
1997
  true,
1942
1998
  service,
1943
1999
  convId,
1944
- controller
2000
+ controller,
2001
+ undefined, // onComplete
2002
+ (errorMsg: string) => handleError(errorMsg) // onError
1945
2003
  );
1946
2004
 
1947
2005
  // Store the context in component state
@@ -2153,6 +2211,9 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
2153
2211
  // Clear thinking blocks for new response
2154
2212
  setThinkingBlocks([]);
2155
2213
  setCurrentThinkingIndex(0);
2214
+
2215
+ // Clear any previous errors
2216
+ setError(null);
2156
2217
 
2157
2218
  // IMPORTANT: Clear the response BEFORE setting new lastKey
2158
2219
  // This prevents the old response from being written to the new history entry
@@ -2302,7 +2363,9 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
2302
2363
  true,
2303
2364
  service,
2304
2365
  convId,
2305
- controller
2366
+ controller,
2367
+ undefined, // onComplete
2368
+ (errorMsg: string) => handleError(errorMsg) // onError
2306
2369
  );
2307
2370
 
2308
2371
  setLastPrompt(nextPromptToSend);
@@ -2404,15 +2467,23 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
2404
2467
  };
2405
2468
 
2406
2469
  const convertMarkdownToHTML = (markdown: string): string => {
2407
- const html = ReactDOMServer.renderToStaticMarkup(
2470
+ const markdownContent = (
2408
2471
  <ReactMarkdown
2409
- className={markdownClass}
2410
2472
  remarkPlugins={[remarkGfm]}
2411
2473
  rehypePlugins={[rehypeRaw]}
2412
2474
  >
2413
2475
  {markdown}
2414
2476
  </ReactMarkdown>
2415
2477
  );
2478
+ const html = ReactDOMServer.renderToStaticMarkup(
2479
+ markdownClass ? (
2480
+ <div className={markdownClass}>
2481
+ {markdownContent}
2482
+ </div>
2483
+ ) : (
2484
+ markdownContent
2485
+ )
2486
+ );
2416
2487
  return html;
2417
2488
  };
2418
2489
 
@@ -2697,17 +2768,59 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
2697
2768
  className={"llm-panel" + (theme === "light" ? "" : " dark-theme")}
2698
2769
  >
2699
2770
  {title && title !== "" ? <div className="title">{title}</div> : null}
2771
+
2772
+ {/* Error Banner */}
2773
+ {error && (
2774
+ <div className="error-banner">
2775
+ <div className="error-banner__icon">
2776
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
2777
+ <circle cx="12" cy="12" r="10" />
2778
+ <line x1="12" x2="12" y1="8" y2="12" />
2779
+ <line x1="12" x2="12.01" y1="16" y2="16" />
2780
+ </svg>
2781
+ </div>
2782
+ <div className="error-banner__content">
2783
+ <div className="error-banner__message">{error.message}</div>
2784
+ {error.code === '413' && (
2785
+ <div className="error-banner__hint">
2786
+ Try starting a new conversation or reducing the amount of information being sent.
2787
+ </div>
2788
+ )}
2789
+ </div>
2790
+ <button
2791
+ className="error-banner__close"
2792
+ onClick={() => setError(null)}
2793
+ aria-label="Dismiss error"
2794
+ >
2795
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
2796
+ <line x1="18" x2="6" y1="6" y2="18" />
2797
+ <line x1="6" x2="18" y1="6" y2="18" />
2798
+ </svg>
2799
+ </button>
2800
+ </div>
2801
+ )}
2802
+
2700
2803
  <div className="responseArea" ref={responseAreaRef}>
2701
2804
  {initialMessage && initialMessage !== "" ? (
2702
2805
  <div className="history-entry">
2703
2806
  <div className="response">
2704
- <ReactMarkdown
2705
- className={markdownClass}
2706
- remarkPlugins={[remarkGfm]}
2707
- rehypePlugins={[rehypeRaw]}
2708
- >
2709
- {initialMessage}
2710
- </ReactMarkdown>
2807
+ {markdownClass ? (
2808
+ <div className={markdownClass}>
2809
+ <ReactMarkdown
2810
+ remarkPlugins={[remarkGfm]}
2811
+ rehypePlugins={[rehypeRaw]}
2812
+ >
2813
+ {initialMessage}
2814
+ </ReactMarkdown>
2815
+ </div>
2816
+ ) : (
2817
+ <ReactMarkdown
2818
+ remarkPlugins={[remarkGfm]}
2819
+ rehypePlugins={[rehypeRaw]}
2820
+ >
2821
+ {initialMessage}
2822
+ </ReactMarkdown>
2823
+ )}
2711
2824
  </div>
2712
2825
  </div>
2713
2826
  ) : null}
@@ -2782,9 +2895,8 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
2782
2895
  // Get the processed content that includes action buttons from history
2783
2896
  // During streaming, use the most recent history entry if it exists
2784
2897
  if (lastKey && history[lastKey] && history[lastKey].content) {
2785
- return (
2898
+ const content = (
2786
2899
  <ReactMarkdown
2787
- className={markdownClass}
2788
2900
  remarkPlugins={[remarkGfm]}
2789
2901
  rehypePlugins={[rehypeRaw]}
2790
2902
  components={{ /*a: CustomLink,*/ code: CodeBlock }}
@@ -2792,22 +2904,34 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
2792
2904
  {history[lastKey].content}
2793
2905
  </ReactMarkdown>
2794
2906
  );
2907
+ return markdownClass ? (
2908
+ <div className={markdownClass}>{content}</div>
2909
+ ) : (
2910
+ content
2911
+ );
2795
2912
  }
2796
2913
 
2797
2914
  // Fallback to cleaned text if no processed history exists yet
2798
2915
  const { cleanedText } = processThinkingTags(
2799
2916
  response || ""
2800
2917
  );
2801
- return cleanedText && cleanedText.length > 0 ? (
2802
- <ReactMarkdown
2803
- className={markdownClass}
2804
- remarkPlugins={[remarkGfm]}
2805
- rehypePlugins={[rehypeRaw]}
2806
- components={{ /*a: CustomLink,*/ code: CodeBlock }}
2807
- >
2808
- {cleanedText}
2809
- </ReactMarkdown>
2810
- ) : null;
2918
+ if (cleanedText && cleanedText.length > 0) {
2919
+ const content = (
2920
+ <ReactMarkdown
2921
+ remarkPlugins={[remarkGfm]}
2922
+ rehypePlugins={[rehypeRaw]}
2923
+ components={{ /*a: CustomLink,*/ code: CodeBlock }}
2924
+ >
2925
+ {cleanedText}
2926
+ </ReactMarkdown>
2927
+ );
2928
+ return markdownClass ? (
2929
+ <div className={markdownClass}>{content}</div>
2930
+ ) : (
2931
+ content
2932
+ );
2933
+ }
2934
+ return null;
2811
2935
  })()}
2812
2936
  </div>
2813
2937
  ) : (
@@ -2818,14 +2942,25 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
2818
2942
  renderThinkingBlocks()}
2819
2943
 
2820
2944
  {/* Show the main content (cleaned of thinking tags) */}
2821
- <ReactMarkdown
2822
- className={markdownClass}
2823
- remarkPlugins={[remarkGfm]}
2824
- rehypePlugins={[rehypeRaw]}
2825
- components={{ /*a: CustomLink,*/ code: CodeBlock }}
2826
- >
2827
- {processThinkingTags(historyEntry.content).cleanedText}
2828
- </ReactMarkdown>
2945
+ {markdownClass ? (
2946
+ <div className={markdownClass}>
2947
+ <ReactMarkdown
2948
+ remarkPlugins={[remarkGfm]}
2949
+ rehypePlugins={[rehypeRaw]}
2950
+ components={{ /*a: CustomLink,*/ code: CodeBlock }}
2951
+ >
2952
+ {processThinkingTags(historyEntry.content).cleanedText}
2953
+ </ReactMarkdown>
2954
+ </div>
2955
+ ) : (
2956
+ <ReactMarkdown
2957
+ remarkPlugins={[remarkGfm]}
2958
+ rehypePlugins={[rehypeRaw]}
2959
+ components={{ /*a: CustomLink,*/ code: CodeBlock }}
2960
+ >
2961
+ {processThinkingTags(historyEntry.content).cleanedText}
2962
+ </ReactMarkdown>
2963
+ )}
2829
2964
  </div>
2830
2965
  )}
2831
2966
 
@@ -3158,6 +3293,7 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
3158
3293
  setIsToolInfoModalOpen(false);
3159
3294
  setToolInfoData(null);
3160
3295
  setJustReset(true);
3296
+ setError(null); // Clear any errors
3161
3297
 
3162
3298
  // Create a new AbortController for future requests
3163
3299
  setLastController(new AbortController());
@@ -59,3 +59,4 @@ export default Button;
59
59
 
60
60
 
61
61
 
62
+
@@ -155,3 +155,4 @@ export default Dialog;
155
155
 
156
156
 
157
157
 
158
+
@@ -35,3 +35,4 @@ export default Input;
35
35
 
36
36
 
37
37
 
38
+
@@ -158,3 +158,4 @@ export default Select;
158
158
 
159
159
 
160
160
 
161
+
@@ -0,0 +1,66 @@
1
+ import React from 'react';
2
+
3
+ interface ToolInfoModalProps {
4
+ isOpen: boolean;
5
+ onClose: () => void;
6
+ data: { calls: any[]; responses: any[] } | null;
7
+ }
8
+
9
+ const ToolInfoModal: React.FC<ToolInfoModalProps> = ({
10
+ isOpen,
11
+ onClose,
12
+ data,
13
+ }) => {
14
+ if (!isOpen || !data) return null;
15
+
16
+ return (
17
+ <div className="ai-chat-modal-overlay" onClick={onClose}>
18
+ <div
19
+ className="ai-chat-modal-content ai-chat-tool-info-modal"
20
+ onClick={(e) => e.stopPropagation()}
21
+ >
22
+ <div className="ai-chat-modal-header">
23
+ <h3>Tool Information</h3>
24
+ <button
25
+ className="ai-chat-modal-close"
26
+ onClick={onClose}
27
+ aria-label="Close"
28
+ >
29
+ ×
30
+ </button>
31
+ </div>
32
+
33
+ <div className="ai-chat-modal-body ai-chat-tool-info-container">
34
+ <div className="ai-chat-tool-info-section">
35
+ <h4>Tool Calls</h4>
36
+ <textarea
37
+ className="ai-chat-tool-info-json"
38
+ readOnly
39
+ value={JSON.stringify(data.calls, null, 2)}
40
+ />
41
+ </div>
42
+ <div className="ai-chat-tool-info-section">
43
+ <h4>Tool Responses</h4>
44
+ <textarea
45
+ className="ai-chat-tool-info-json"
46
+ readOnly
47
+ value={JSON.stringify(data.responses, null, 2)}
48
+ />
49
+ </div>
50
+ </div>
51
+
52
+ <div className="ai-chat-modal-footer">
53
+ <button
54
+ className="ai-chat-modal-button ai-chat-modal-button--primary"
55
+ onClick={onClose}
56
+ >
57
+ Close
58
+ </button>
59
+ </div>
60
+ </div>
61
+ </div>
62
+ );
63
+ };
64
+
65
+ export default ToolInfoModal;
66
+
@@ -75,3 +75,4 @@ export default Tooltip;
75
75
 
76
76
 
77
77
 
78
+
@@ -22,3 +22,4 @@ export type { DialogProps, DialogFooterProps } from './Dialog';
22
22
 
23
23
 
24
24
 
25
+
@@ -106,6 +106,7 @@ export function useAgentRegistry(
106
106
 
107
107
  const data = await response.json();
108
108
  const agentData = Array.isArray(data) ? data[0] : data;
109
+
109
110
 
110
111
  if (!agentData) {
111
112
  throw new Error(`No data returned for agent ${agentId}`);