@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
|
@@ -0,0 +1,830 @@
|
|
|
1
|
+
# AIChatPanel Port Inventory
|
|
2
|
+
## Complete Feature Gap Analysis: ChatPanel → AIChatPanel
|
|
3
|
+
|
|
4
|
+
**Last Updated:** Dec 15, 2025
|
|
5
|
+
**Status:** 🚨 CRITICAL - Multiple core features missing
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Executive Summary
|
|
10
|
+
|
|
11
|
+
This document catalogs ALL features present in `ChatPanel.tsx` that are missing from `AIChatPanel.tsx`. This is the authoritative checklist for achieving feature parity.
|
|
12
|
+
|
|
13
|
+
**Current Status:**
|
|
14
|
+
- ✅ Implemented: ~60%
|
|
15
|
+
- ❌ Missing: ~40%
|
|
16
|
+
- 🔶 Partial: Features that exist but differ in implementation
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 1. Missing Props (12 props)
|
|
21
|
+
|
|
22
|
+
### 1.1 UI Customization Props
|
|
23
|
+
| Prop | Type | Default (ChatPanel) | Status | Notes |
|
|
24
|
+
|------|------|---------------------|--------|-------|
|
|
25
|
+
| `cssUrl` | `string` | `""` | ❌ | Injects custom CSS (URL or inline) |
|
|
26
|
+
| `markdownClass` | `string` | `null` | ❌ | Custom class for ReactMarkdown |
|
|
27
|
+
| `width` | `string` | `"300px"` | ❌ | Panel width |
|
|
28
|
+
| `height` | `string` | `"100vh"` | ❌ | Panel height |
|
|
29
|
+
| `scrollToEnd` | `boolean` | `false` | ❌ | Auto-scroll behavior |
|
|
30
|
+
| `prismStyle` | `PrismStyle` | theme-based | ❌ | Code syntax highlighting style |
|
|
31
|
+
|
|
32
|
+
**ChatPanel Implementation:**
|
|
33
|
+
```typescript:22:41:src/ChatPanel.tsx
|
|
34
|
+
export interface ChatPanelProps {
|
|
35
|
+
project_id: string;
|
|
36
|
+
initialPrompt?: string;
|
|
37
|
+
initialMessage?: string;
|
|
38
|
+
title?: string;
|
|
39
|
+
placeholder?: string;
|
|
40
|
+
hideInitialPrompt?: boolean;
|
|
41
|
+
customer?: LLMAsAServiceCustomer;
|
|
42
|
+
messages?: { role: "user" | "assistant"; content: string }[];
|
|
43
|
+
data?: { key: string; data: string }[];
|
|
44
|
+
thumbsUpClick?: (callId: string) => void;
|
|
45
|
+
thumbsDownClick?: (callId: string) => void;
|
|
46
|
+
theme?: "light" | "dark";
|
|
47
|
+
cssUrl?: string;
|
|
48
|
+
markdownClass?: string;
|
|
49
|
+
width?: string;
|
|
50
|
+
height?: string;
|
|
51
|
+
url?: string | null;
|
|
52
|
+
scrollToEnd?: boolean;
|
|
53
|
+
prismStyle?: PrismStyle;
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Usage in ChatPanel:**
|
|
57
|
+
- `cssUrl`: Lines 994-1038 - Dynamic CSS injection via `<link>` or `<style>` elements
|
|
58
|
+
- `markdownClass`: Line 2800 - Applied to `ReactMarkdown` component
|
|
59
|
+
- `width`/`height`: Line 2759 - Applied to root div inline styles
|
|
60
|
+
- `prismStyle`: Line 114 - Passed to `SyntaxHighlighter`
|
|
61
|
+
|
|
62
|
+
### 1.2 Email & CTA Props
|
|
63
|
+
| Prop | Type | Default | Status | Notes |
|
|
64
|
+
|------|------|---------|--------|-------|
|
|
65
|
+
| `showSaveButton` | `boolean` | `true` | ❌ | Save conversation to HTML |
|
|
66
|
+
| `showEmailButton` | `boolean` | `true` | ❌ | Email conversation |
|
|
67
|
+
| `showCallToAction` | `boolean` | `false` | ❌ | Show CTA button |
|
|
68
|
+
| `callToActionButtonText` | `string` | `"Submit"` | ❌ | CTA button label |
|
|
69
|
+
| `callToActionEmailAddress` | `string` | `""` | ❌ | CTA recipient email |
|
|
70
|
+
| `callToActionEmailSubject` | `string` | `"Agent CTA submitted"` | ❌ | CTA email subject |
|
|
71
|
+
|
|
72
|
+
**ChatPanel Implementation:**
|
|
73
|
+
```typescript:60:72:src/ChatPanel.tsx
|
|
74
|
+
showSaveButton?: boolean;
|
|
75
|
+
showEmailButton?: boolean;
|
|
76
|
+
showNewConversationButton?: boolean;
|
|
77
|
+
followOnQuestions?: string[];
|
|
78
|
+
clearFollowOnQuestionsNextPrompt?: boolean;
|
|
79
|
+
followOnPrompt?: string;
|
|
80
|
+
showPoweredBy?: boolean;
|
|
81
|
+
agent?: string | null;
|
|
82
|
+
conversation?: string | null;
|
|
83
|
+
showCallToAction?: boolean;
|
|
84
|
+
callToActionButtonText?: string;
|
|
85
|
+
callToActionEmailAddress?: string;
|
|
86
|
+
callToActionEmailSubject?: string;
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Rendering Location:** Lines 3206-3340 - Button container at bottom of panel
|
|
90
|
+
|
|
91
|
+
### 1.3 Customer Email Capture Props
|
|
92
|
+
| Prop | Type | Default | Status | Notes |
|
|
93
|
+
|------|------|---------|--------|-------|
|
|
94
|
+
| `customerEmailCaptureMode` | `"HIDE"` \| `"OPTIONAL"` \| `"REQUIRED"` | `"HIDE"` | ❌ | Email capture behavior |
|
|
95
|
+
| `customerEmailCapturePlaceholder` | `string` | `"Please enter your email..."` | ❌ | Email input placeholder |
|
|
96
|
+
|
|
97
|
+
**ChatPanel Implementation:**
|
|
98
|
+
- State management: Lines 171-184
|
|
99
|
+
- Validation logic: Lines 141-144, 2705-2710
|
|
100
|
+
- UI rendering: Lines 3133-3182 (email input panel above textarea)
|
|
101
|
+
|
|
102
|
+
### 1.4 Initial Messages Prop
|
|
103
|
+
| Prop | Type | Default | Status | Notes |
|
|
104
|
+
|------|------|---------|--------|-------|
|
|
105
|
+
| `messages` | `{ role: "user" \| "assistant"; content: string }[]` | `[]` | ❌ | Pre-populate conversation history |
|
|
106
|
+
|
|
107
|
+
**ChatPanel Usage:** Line 102 - Default value only, not actively used in current implementation
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 2. Missing Components (2 components)
|
|
112
|
+
|
|
113
|
+
### 2.1 EmailModal
|
|
114
|
+
**Status:** ❌ Missing
|
|
115
|
+
|
|
116
|
+
**File:** `src/EmailModal.tsx`
|
|
117
|
+
```typescript:1:57:src/EmailModal.tsx
|
|
118
|
+
import React, { useState } from "react";
|
|
119
|
+
import "./ChatPanel.css"; // Ensure this file contains the modal styles
|
|
120
|
+
|
|
121
|
+
interface EmailModalProps {
|
|
122
|
+
isOpen: boolean;
|
|
123
|
+
onClose: () => void;
|
|
124
|
+
onSend: (to: string, from: string) => void;
|
|
125
|
+
defaultEmail?: string;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const EmailModal: React.FC<EmailModalProps> = ({ isOpen, onClose, onSend, defaultEmail }) => {
|
|
129
|
+
const [email, setEmail] = useState("");
|
|
130
|
+
const [emailFrom, setEmailFrom] = useState(defaultEmail || "");
|
|
131
|
+
|
|
132
|
+
const handleSend = () => {
|
|
133
|
+
onSend(email, emailFrom);
|
|
134
|
+
onClose();
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
if (!isOpen) return null;
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<div className="modal-overlay">
|
|
141
|
+
<div className="modal-content">
|
|
142
|
+
<p className="modal-text">
|
|
143
|
+
Email Addresses
|
|
144
|
+
<br /> (If multiple, comma separate them)
|
|
145
|
+
</p>
|
|
146
|
+
<p>
|
|
147
|
+
<input
|
|
148
|
+
type="email"
|
|
149
|
+
width="100%"
|
|
150
|
+
value={email}
|
|
151
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
152
|
+
placeholder="To email address"
|
|
153
|
+
/>
|
|
154
|
+
</p>
|
|
155
|
+
<p>
|
|
156
|
+
<input
|
|
157
|
+
type="email"
|
|
158
|
+
width="100%"
|
|
159
|
+
value={emailFrom}
|
|
160
|
+
onChange={(e) => setEmailFrom(e.target.value)}
|
|
161
|
+
placeholder="From email address (optional)"
|
|
162
|
+
/>
|
|
163
|
+
</p>
|
|
164
|
+
<div className="modal-buttons">
|
|
165
|
+
<button onClick={onClose}>Cancel</button>
|
|
166
|
+
<button onClick={handleSend}>Send</button>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
);
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export default EmailModal;
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Integration in ChatPanel:**
|
|
177
|
+
- Import: Line 19
|
|
178
|
+
- State: Line 161
|
|
179
|
+
- Usage: Lines 3345-3349
|
|
180
|
+
- Trigger: Line 3303 (Email button click)
|
|
181
|
+
|
|
182
|
+
### 2.2 ToolInfoModal
|
|
183
|
+
**Status:** ❌ Missing
|
|
184
|
+
|
|
185
|
+
**File:** `src/ToolInfoModal.tsx`
|
|
186
|
+
```typescript:1:50:src/ToolInfoModal.tsx
|
|
187
|
+
import React, { useEffect } from "react";
|
|
188
|
+
import "./ChatPanel.css"; // Reuse styles or create specific ones
|
|
189
|
+
|
|
190
|
+
interface ToolInfoModalProps {
|
|
191
|
+
isOpen: boolean;
|
|
192
|
+
onClose: () => void;
|
|
193
|
+
data: { calls: any[]; responses: any[] } | null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const ToolInfoModal: React.FC<ToolInfoModalProps> = ({
|
|
197
|
+
isOpen,
|
|
198
|
+
onClose,
|
|
199
|
+
data,
|
|
200
|
+
}) => {
|
|
201
|
+
if (!isOpen || !data) return null;
|
|
202
|
+
|
|
203
|
+
return (
|
|
204
|
+
<div className="modal-overlay" onClick={onClose}>
|
|
205
|
+
<div
|
|
206
|
+
className="modal-content tool-info-modal-content"
|
|
207
|
+
onClick={(e) => e.stopPropagation()}
|
|
208
|
+
>
|
|
209
|
+
<div className="tool-info-container">
|
|
210
|
+
<div className="tool-info-section">
|
|
211
|
+
<b>Tool Calls</b>
|
|
212
|
+
<textarea
|
|
213
|
+
className="tool-info-json"
|
|
214
|
+
readOnly
|
|
215
|
+
value={JSON.stringify(data.calls, null, 2)}
|
|
216
|
+
/>
|
|
217
|
+
</div>
|
|
218
|
+
<div className="tool-info-section">
|
|
219
|
+
<b>Tool Responses</b>
|
|
220
|
+
<textarea
|
|
221
|
+
className="tool-info-json"
|
|
222
|
+
readOnly
|
|
223
|
+
value={JSON.stringify(data.responses, null, 2)}
|
|
224
|
+
/>
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
<div className="modal-buttons">
|
|
228
|
+
<button onClick={onClose}>Close</button>
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
);
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
export default ToolInfoModal;
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**Integration in ChatPanel:**
|
|
239
|
+
- Import: Line 20
|
|
240
|
+
- State: Lines 162-166
|
|
241
|
+
- Rendering: Lines 3078-3082
|
|
242
|
+
- Trigger: Lines 3049-3071 (Tool info button in action buttons)
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## 3. Missing Features (7 major features)
|
|
247
|
+
|
|
248
|
+
### 3.1 Save to HTML
|
|
249
|
+
**Status:** ❌ Missing
|
|
250
|
+
|
|
251
|
+
**Description:** Download conversation as standalone HTML file
|
|
252
|
+
|
|
253
|
+
**ChatPanel Implementation:**
|
|
254
|
+
```typescript:2574:2598
|
|
255
|
+
const convertHistoryToHTML = (history: {
|
|
256
|
+
[key: string]: HistoryEntry;
|
|
257
|
+
}): string => {
|
|
258
|
+
return `
|
|
259
|
+
<!DOCTYPE html>
|
|
260
|
+
<html>
|
|
261
|
+
<head><title>Conversation History</title></head>
|
|
262
|
+
<body>
|
|
263
|
+
${Object.entries(history)
|
|
264
|
+
.map(
|
|
265
|
+
([prompt, historyEntry]) => `
|
|
266
|
+
<div>
|
|
267
|
+
<p><strong>User:</strong> ${formatPromptForDisplay(prompt)}</p>
|
|
268
|
+
<p><strong>Assistant:</strong></p>
|
|
269
|
+
${ReactDOMServer.renderToStaticMarkup(
|
|
270
|
+
<ReactMarkdown>{historyEntry.content}</ReactMarkdown>
|
|
271
|
+
)}
|
|
272
|
+
</div>
|
|
273
|
+
`
|
|
274
|
+
)
|
|
275
|
+
.join("")}
|
|
276
|
+
</body>
|
|
277
|
+
</html>
|
|
278
|
+
`;
|
|
279
|
+
};
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**Save Handler:**
|
|
283
|
+
```typescript:2574:2598
|
|
284
|
+
const saveAsHTMLFile = () => {
|
|
285
|
+
const htmlContent = convertHistoryToHTML(history);
|
|
286
|
+
const blob = new Blob([htmlContent], { type: "text/html" });
|
|
287
|
+
const url = URL.createObjectURL(blob);
|
|
288
|
+
const link = document.createElement("a");
|
|
289
|
+
link.href = url;
|
|
290
|
+
link.download = `conversation-${Date.now()}.html`;
|
|
291
|
+
document.body.appendChild(link);
|
|
292
|
+
link.click();
|
|
293
|
+
document.body.removeChild(link);
|
|
294
|
+
URL.revokeObjectURL(url);
|
|
295
|
+
|
|
296
|
+
interactionClicked(lastCallId, "save");
|
|
297
|
+
};
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
**Dependencies:**
|
|
301
|
+
- Import: Line 11 - `import ReactDOMServer from "react-dom/server"`
|
|
302
|
+
- Button: Lines 3287-3300
|
|
303
|
+
|
|
304
|
+
### 3.2 Email Sharing
|
|
305
|
+
**Status:** ❌ Missing
|
|
306
|
+
|
|
307
|
+
**Description:** Share conversation via email using API
|
|
308
|
+
|
|
309
|
+
**API Endpoint:** `POST ${publicAPIUrl}/share/email`
|
|
310
|
+
|
|
311
|
+
**Handler:**
|
|
312
|
+
```typescript:2586:2605
|
|
313
|
+
const sendShareEmail = async (to: string, from: string) => {
|
|
314
|
+
const emailList = to.split(",").map((email) => email.trim());
|
|
315
|
+
|
|
316
|
+
await fetch(`${publicAPIUrl}/share/email`, {
|
|
317
|
+
method: "POST",
|
|
318
|
+
headers: {
|
|
319
|
+
"Content-Type": "application/json",
|
|
320
|
+
},
|
|
321
|
+
body: JSON.stringify({
|
|
322
|
+
to: emailList,
|
|
323
|
+
from: from,
|
|
324
|
+
subject: "Your conversation from " + title,
|
|
325
|
+
html: convertHistoryToHTML(history),
|
|
326
|
+
project_id: project_id ?? "",
|
|
327
|
+
customer: currentCustomer,
|
|
328
|
+
history: history,
|
|
329
|
+
title: title,
|
|
330
|
+
}),
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
await interactionClicked(lastCallId, "email", from);
|
|
334
|
+
};
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
**Button & Modal:** Lines 3302-3320 (Email button), Lines 3345-3349 (EmailModal)
|
|
338
|
+
|
|
339
|
+
### 3.3 Call-to-Action (CTA) System
|
|
340
|
+
**Status:** ❌ Missing
|
|
341
|
+
|
|
342
|
+
**Description:** Custom button that emails conversation to specified address
|
|
343
|
+
|
|
344
|
+
**State Management:**
|
|
345
|
+
- `callToActionSent`: Line 181
|
|
346
|
+
- `CTAClickedButNoEmail`: Line 182
|
|
347
|
+
|
|
348
|
+
**Handler:**
|
|
349
|
+
```typescript:2607:2628
|
|
350
|
+
const sendCallToActionEmail = async (from: string) => {
|
|
351
|
+
const r = await fetch(`${publicAPIUrl}/share/email`, {
|
|
352
|
+
method: "POST",
|
|
353
|
+
headers: {
|
|
354
|
+
"Content-Type": "application/json",
|
|
355
|
+
},
|
|
356
|
+
body: JSON.stringify({
|
|
357
|
+
to: callToActionEmailAddress,
|
|
358
|
+
from: from,
|
|
359
|
+
subject: `${callToActionEmailSubject} from ${from}`,
|
|
360
|
+
html: convertHistoryToHTML(history),
|
|
361
|
+
project_id: project_id ?? "",
|
|
362
|
+
customer: currentCustomer,
|
|
363
|
+
history: history,
|
|
364
|
+
title: title,
|
|
365
|
+
}),
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
await interactionClicked(lastCallId, "cta", from);
|
|
369
|
+
|
|
370
|
+
setCallToActionSent(true);
|
|
371
|
+
};
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
**Button:** Lines 3323-3340
|
|
375
|
+
|
|
376
|
+
### 3.4 Customer Email Capture Panel
|
|
377
|
+
**Status:** ❌ Missing
|
|
378
|
+
|
|
379
|
+
**Description:** Inline email input for capturing customer email (OPTIONAL or REQUIRED modes)
|
|
380
|
+
|
|
381
|
+
**State:**
|
|
382
|
+
- `emailInput`: Line 171
|
|
383
|
+
- `emailInputSet`: Line 174
|
|
384
|
+
- `emailValid`: Line 177
|
|
385
|
+
- `showEmailPanel`: Line 178
|
|
386
|
+
- `emailClickedButNoEmail`: Line 184
|
|
387
|
+
|
|
388
|
+
**Rendering:** Lines 3133-3182
|
|
389
|
+
```typescript:3133:3182
|
|
390
|
+
{showEmailPanel && !emailInputSet && (
|
|
391
|
+
<div className="customer-email-panel">
|
|
392
|
+
<input
|
|
393
|
+
type="email"
|
|
394
|
+
className="customer-email-input"
|
|
395
|
+
value={emailInput}
|
|
396
|
+
onChange={(e) => {
|
|
397
|
+
setEmailInput(e.target.value);
|
|
398
|
+
setEmailValid(isEmailAddress(e.target.value));
|
|
399
|
+
}}
|
|
400
|
+
onKeyDown={(e) => {
|
|
401
|
+
if (e.key === "Enter") {
|
|
402
|
+
if (isEmailAddress(emailInput)) {
|
|
403
|
+
setEmailInputSet(true);
|
|
404
|
+
setCurrentCustomer({
|
|
405
|
+
...currentCustomer,
|
|
406
|
+
customer_user_email: emailInput,
|
|
407
|
+
});
|
|
408
|
+
interactionClicked("", "emailcapture", emailInput);
|
|
409
|
+
} else {
|
|
410
|
+
setEmailValid(false);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}}
|
|
414
|
+
placeholder={customerEmailCapturePlaceholder}
|
|
415
|
+
/>
|
|
416
|
+
<button
|
|
417
|
+
className="email-set-button"
|
|
418
|
+
onClick={() => {
|
|
419
|
+
if (isEmailAddress(emailInput)) {
|
|
420
|
+
setEmailInputSet(true);
|
|
421
|
+
setCurrentCustomer({
|
|
422
|
+
...currentCustomer,
|
|
423
|
+
customer_user_email: emailInput,
|
|
424
|
+
});
|
|
425
|
+
interactionClicked("", "emailcapture", emailInput);
|
|
426
|
+
} else {
|
|
427
|
+
setEmailValid(false);
|
|
428
|
+
}
|
|
429
|
+
}}
|
|
430
|
+
>
|
|
431
|
+
Set Email
|
|
432
|
+
</button>
|
|
433
|
+
{!emailValid && (
|
|
434
|
+
<div className="email-error-message">
|
|
435
|
+
Please enter a valid email
|
|
436
|
+
</div>
|
|
437
|
+
)}
|
|
438
|
+
</div>
|
|
439
|
+
)}
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
**Validation:** Lines 2705-2710
|
|
443
|
+
|
|
444
|
+
### 3.5 Tool Approval UI (MCP Tools)
|
|
445
|
+
**Status:** ❌ Missing
|
|
446
|
+
|
|
447
|
+
**Description:** User approval interface for MCP tool calls (once/session/always)
|
|
448
|
+
|
|
449
|
+
**State:**
|
|
450
|
+
- `pendingToolRequests`: Line 203
|
|
451
|
+
- `sessionApprovedTools`: Line 230
|
|
452
|
+
- `alwaysApprovedTools`: Line 231
|
|
453
|
+
|
|
454
|
+
**Handler:**
|
|
455
|
+
```typescript:2716:2734
|
|
456
|
+
const handleToolApproval = (
|
|
457
|
+
toolName: string,
|
|
458
|
+
scope: "once" | "session" | "always"
|
|
459
|
+
) => {
|
|
460
|
+
if (scope === "session" || scope === "always") {
|
|
461
|
+
setSessionApprovedTools((p) => Array.from(new Set([...p, toolName])));
|
|
462
|
+
}
|
|
463
|
+
if (scope === "always") {
|
|
464
|
+
setAlwaysApprovedTools((p) => Array.from(new Set([...p, toolName])));
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// process and remove just this tool's calls
|
|
468
|
+
const requestsToRun = pendingToolRequests.filter(
|
|
469
|
+
(r) => r.toolName === toolName
|
|
470
|
+
);
|
|
471
|
+
processGivenToolRequests(requestsToRun);
|
|
472
|
+
setPendingToolRequests((p) => p.filter((r) => r.toolName !== toolName));
|
|
473
|
+
};
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
**Rendering:** Lines 2927-2981 (Approval panel above action buttons when tools pending)
|
|
477
|
+
|
|
478
|
+
**Auto-approval Effect:** Lines 2737-2754
|
|
479
|
+
|
|
480
|
+
### 3.6 Progressive Actions System
|
|
481
|
+
**Status:** ❌ Missing (AIChatPanel has basic actions, but not progressive)
|
|
482
|
+
|
|
483
|
+
**Description:** Advanced action button system with streaming support and state management
|
|
484
|
+
|
|
485
|
+
**Key Differences:**
|
|
486
|
+
1. **Disabled during streaming:** Buttons show as placeholders until response completes
|
|
487
|
+
2. **Stable button IDs:** Registry system prevents duplicate buttons during streaming
|
|
488
|
+
3. **Deferred attachment:** Event handlers only attached once streaming finishes
|
|
489
|
+
4. **History vs Streaming:** Different processing paths for historical vs live content
|
|
490
|
+
|
|
491
|
+
**State:**
|
|
492
|
+
- `allActions`: Line 190 - Merged array of user + built-in actions
|
|
493
|
+
- `pendingButtonAttachments`: Line 247
|
|
494
|
+
- `actionMatchRegistry`: Line 248 - Map for stable button IDs
|
|
495
|
+
- `deferredActionsRef`: Line 249 - Buttons to finalize after streaming
|
|
496
|
+
- `finalizedButtonsRef`: Line 251 - Prevent duplicate finalization
|
|
497
|
+
- `actionSequenceRef`: Line 252 - Incremental ID generator
|
|
498
|
+
- `buttonActionRegistry`: Line 254 - Fallback for event delegation
|
|
499
|
+
|
|
500
|
+
**Processing Function:** Lines 761-939 `processActionsWithContext`
|
|
501
|
+
|
|
502
|
+
**Finalization Logic:** Lines 550-730 (useEffect for button attachment)
|
|
503
|
+
|
|
504
|
+
### 3.7 Dynamic CSS Injection
|
|
505
|
+
**Status:** ❌ Missing
|
|
506
|
+
|
|
507
|
+
**Description:** Inject custom CSS from URL or inline string
|
|
508
|
+
|
|
509
|
+
**Implementation:** Lines 993-1038
|
|
510
|
+
```typescript:993:1038
|
|
511
|
+
useEffect(() => {
|
|
512
|
+
// Clean up any previously added CSS from this component
|
|
513
|
+
const existingLinks = document.querySelectorAll(
|
|
514
|
+
'link[data-source="llmasaservice-ui"]'
|
|
515
|
+
);
|
|
516
|
+
existingLinks.forEach((link) => link.parentNode?.removeChild(link));
|
|
517
|
+
|
|
518
|
+
const existingStyles = document.querySelectorAll(
|
|
519
|
+
'style[data-source="llmasaservice-ui"]'
|
|
520
|
+
);
|
|
521
|
+
existingStyles.forEach((style) => style.parentNode?.removeChild(style));
|
|
522
|
+
|
|
523
|
+
if (cssUrl) {
|
|
524
|
+
if (cssUrl.startsWith("http://") || cssUrl.startsWith("https://")) {
|
|
525
|
+
// If it's a URL, create a link element
|
|
526
|
+
const link = document.createElement("link");
|
|
527
|
+
link.href = cssUrl;
|
|
528
|
+
link.rel = "stylesheet";
|
|
529
|
+
// Add a data attribute to identify and remove this link later if needed
|
|
530
|
+
link.setAttribute("data-source", "llmasaservice-ui");
|
|
531
|
+
document.head.appendChild(link);
|
|
532
|
+
//console.log("Added CSS link", link);
|
|
533
|
+
} else {
|
|
534
|
+
// If it's a CSS string, create a style element
|
|
535
|
+
const style = document.createElement("style");
|
|
536
|
+
style.textContent = cssUrl;
|
|
537
|
+
// Add a data attribute to identify and remove this style later if needed
|
|
538
|
+
style.setAttribute("data-source", "llmasaservice-ui");
|
|
539
|
+
document.head.appendChild(style);
|
|
540
|
+
//console.log("Added inline CSS");
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Clean up when component unmounts
|
|
545
|
+
return () => {
|
|
546
|
+
const links = document.querySelectorAll(
|
|
547
|
+
'link[data-source="llmasaservice-ui"]'
|
|
548
|
+
);
|
|
549
|
+
links.forEach((link) => link.parentNode?.removeChild(link));
|
|
550
|
+
|
|
551
|
+
const styles = document.querySelectorAll(
|
|
552
|
+
'style[data-source="llmasaservice-ui"]'
|
|
553
|
+
);
|
|
554
|
+
styles.forEach((style) => style.parentNode?.removeChild(style));
|
|
555
|
+
};
|
|
556
|
+
}, [cssUrl]);
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
---
|
|
560
|
+
|
|
561
|
+
## 4. Missing State Variables (15+ state variables)
|
|
562
|
+
|
|
563
|
+
| State Variable | Type | Purpose | ChatPanel Line |
|
|
564
|
+
|----------------|------|---------|----------------|
|
|
565
|
+
| `nextPrompt` | `string` | Textarea value (OLD pattern) | 146 |
|
|
566
|
+
| `hasScroll` | `boolean` | Tracks if scroll exists | 155 |
|
|
567
|
+
| `textareaRef` | `Ref<HTMLTextAreaElement>` | Direct textarea ref | 158 |
|
|
568
|
+
| `isAtBottom` | `boolean` | Scroll position tracking | 160 |
|
|
569
|
+
| `isEmailModalOpen` | `boolean` | Email modal state | 161 |
|
|
570
|
+
| `isToolInfoModalOpen` | `boolean` | Tool info modal state | 162 |
|
|
571
|
+
| `toolInfoData` | `{calls, responses}` | Tool JSON data for modal | 163-166 |
|
|
572
|
+
| `emailInput` | `string` | Customer email input | 171 |
|
|
573
|
+
| `emailInputSet` | `boolean` | Email confirmed flag | 174 |
|
|
574
|
+
| `emailValid` | `boolean` | Email validation state | 177 |
|
|
575
|
+
| `showEmailPanel` | `boolean` | Show email capture UI | 178 |
|
|
576
|
+
| `callToActionSent` | `boolean` | CTA submitted flag | 181 |
|
|
577
|
+
| `CTAClickedButNoEmail` | `boolean` | CTA error state | 182 |
|
|
578
|
+
| `emailSent` | `boolean` | Email sent confirmation | 183 |
|
|
579
|
+
| `emailClickedButNoEmail` | `boolean` | Email error state | 184 |
|
|
580
|
+
| `allActions` | `Action[]` | Combined user+built-in actions | 190 |
|
|
581
|
+
| `pendingButtonAttachments` | `ButtonAttachment[]` | Buttons to finalize | 247 |
|
|
582
|
+
| `pendingToolRequests` | `ToolRequest[]` | MCP tools awaiting approval | 203 |
|
|
583
|
+
| `sessionApprovedTools` | `string[]` | Tools approved this session | 230 |
|
|
584
|
+
| `alwaysApprovedTools` | `string[]` | Tools approved permanently | 231 |
|
|
585
|
+
|
|
586
|
+
---
|
|
587
|
+
|
|
588
|
+
## 5. Missing Helper Functions (10+ functions)
|
|
589
|
+
|
|
590
|
+
| Function | Purpose | ChatPanel Lines |
|
|
591
|
+
|----------|---------|-----------------|
|
|
592
|
+
| `isEmailAddress()` | Email validation regex | 141-144 |
|
|
593
|
+
| `extractValue()` | Template variable substitution | 1040-1065 |
|
|
594
|
+
| `openUrlActionCallback()` | Built-in action: open URL | 1067-1075 |
|
|
595
|
+
| `copyToClipboardCallback()` | Built-in action: copy text | 1077-1082 |
|
|
596
|
+
| `showAlertCallback()` | Built-in action: show alert | 1085-1090 |
|
|
597
|
+
| `sendFollowOnPromptCallback()` | Built-in action: send message | 1092-1099 |
|
|
598
|
+
| `processActionsWithContext()` | Advanced action processing | 761-939 |
|
|
599
|
+
| `convertHistoryToHTML()` | Convert conversation to HTML | 2574-2598 |
|
|
600
|
+
| `saveAsHTMLFile()` | Download HTML file | 2561-2573 |
|
|
601
|
+
| `sendShareEmail()` | Email conversation | 2586-2605 |
|
|
602
|
+
| `sendCallToActionEmail()` | CTA email handler | 2607-2628 |
|
|
603
|
+
| `isDisabledDueToNoEmail()` | Disable logic for email capture | 2705-2710 |
|
|
604
|
+
| `getUniqueToolNames()` | Dedupe tool names | 2713-2714 |
|
|
605
|
+
| `handleToolApproval()` | MCP tool approval handler | 2716-2734 |
|
|
606
|
+
|
|
607
|
+
---
|
|
608
|
+
|
|
609
|
+
## 6. Rendering Differences
|
|
610
|
+
|
|
611
|
+
### 6.1 Button Container Layout
|
|
612
|
+
**ChatPanel:** Lines 3206-3340
|
|
613
|
+
- Separate container div (`.button-container-actions`)
|
|
614
|
+
- Save, Email, New Conversation buttons in footer
|
|
615
|
+
- CTA button conditional rendering
|
|
616
|
+
|
|
617
|
+
**AIChatPanel:** Lines 2005-2016
|
|
618
|
+
- Only New Conversation button
|
|
619
|
+
- No Save/Email/CTA buttons
|
|
620
|
+
|
|
621
|
+
### 6.2 Tool Info Button
|
|
622
|
+
**ChatPanel:** Lines 3049-3071
|
|
623
|
+
- Button in action container
|
|
624
|
+
- Shows tool call/response JSON
|
|
625
|
+
- Only visible if `hasToolData`
|
|
626
|
+
|
|
627
|
+
**AIChatPanel:** ❌ Missing entirely
|
|
628
|
+
|
|
629
|
+
### 6.3 Email Capture Panel
|
|
630
|
+
**ChatPanel:** Lines 3133-3182
|
|
631
|
+
- Inline panel above textarea
|
|
632
|
+
- Conditional based on `customerEmailCaptureMode`
|
|
633
|
+
- Validation and error messages
|
|
634
|
+
|
|
635
|
+
**AIChatPanel:** ❌ Missing entirely
|
|
636
|
+
|
|
637
|
+
### 6.4 Inline Styles
|
|
638
|
+
**ChatPanel:** Line 2759
|
|
639
|
+
```typescript
|
|
640
|
+
<div
|
|
641
|
+
style={{ width: width, height: height }}
|
|
642
|
+
className={"llm-panel" + (theme === "light" ? "" : " dark-theme")}
|
|
643
|
+
>
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
**AIChatPanel:** Line 1827
|
|
647
|
+
```typescript
|
|
648
|
+
<div className={panelClasses}>
|
|
649
|
+
```
|
|
650
|
+
No width/height props applied.
|
|
651
|
+
|
|
652
|
+
---
|
|
653
|
+
|
|
654
|
+
## 7. CSS Dependencies
|
|
655
|
+
|
|
656
|
+
### Missing CSS Classes (from ChatPanel.css)
|
|
657
|
+
The following CSS classes are used in ChatPanel but don't exist in AIChatPanel.css:
|
|
658
|
+
|
|
659
|
+
**Email & Modals:**
|
|
660
|
+
- `.modal-overlay`
|
|
661
|
+
- `.modal-content`
|
|
662
|
+
- `.modal-text`
|
|
663
|
+
- `.modal-buttons`
|
|
664
|
+
- `.tool-info-modal-content`
|
|
665
|
+
- `.tool-info-container`
|
|
666
|
+
- `.tool-info-section`
|
|
667
|
+
- `.tool-info-json`
|
|
668
|
+
- `.customer-email-panel`
|
|
669
|
+
- `.customer-email-input`
|
|
670
|
+
- `.email-set-button`
|
|
671
|
+
- `.email-error-message`
|
|
672
|
+
|
|
673
|
+
**Buttons:**
|
|
674
|
+
- `.button-container-actions`
|
|
675
|
+
- `.save-button`
|
|
676
|
+
- `.new-conversation-button`
|
|
677
|
+
- `.copy-button`
|
|
678
|
+
- `.thumbs-button`
|
|
679
|
+
- `.icon-svg`
|
|
680
|
+
|
|
681
|
+
**Tool Approval:**
|
|
682
|
+
- `.approve-tools-panel`
|
|
683
|
+
- `.approve-tool-item`
|
|
684
|
+
- `.approve-tools-header`
|
|
685
|
+
- `.approve-tools-buttons`
|
|
686
|
+
- `.approve-tools-button`
|
|
687
|
+
- `.approve-tools-description`
|
|
688
|
+
|
|
689
|
+
**Actions:**
|
|
690
|
+
- `.action-button` (for progressive actions)
|
|
691
|
+
- Disabled state styling for streaming buttons
|
|
692
|
+
|
|
693
|
+
---
|
|
694
|
+
|
|
695
|
+
## 8. Import Differences
|
|
696
|
+
|
|
697
|
+
**ChatPanel Imports (Missing from AIChatPanel):**
|
|
698
|
+
```typescript:11:20
|
|
699
|
+
import ReactDOMServer from "react-dom/server";
|
|
700
|
+
import "./ChatPanel.css";
|
|
701
|
+
import remarkGfm from "remark-gfm";
|
|
702
|
+
import rehypeRaw from "rehype-raw";
|
|
703
|
+
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
|
704
|
+
import PrismStyle from "react-syntax-highlighter";
|
|
705
|
+
import materialDark from "react-syntax-highlighter/dist/esm/styles/prism/material-dark.js";
|
|
706
|
+
import materialLight from "react-syntax-highlighter/dist/esm/styles/prism/material-light.js";
|
|
707
|
+
import EmailModal from "./EmailModal";
|
|
708
|
+
import ToolInfoModal from "./ToolInfoModal";
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
**Present in AIChatPanel:** Lines 7-22
|
|
712
|
+
```typescript
|
|
713
|
+
import React, {
|
|
714
|
+
useCallback,
|
|
715
|
+
useEffect,
|
|
716
|
+
useMemo,
|
|
717
|
+
useRef,
|
|
718
|
+
useState,
|
|
719
|
+
} from 'react';
|
|
720
|
+
import { LLMAsAServiceCustomer, useLLM } from 'llmasaservice-client';
|
|
721
|
+
import ReactMarkdown from 'react-markdown';
|
|
722
|
+
import remarkGfm from 'remark-gfm';
|
|
723
|
+
import rehypeRaw from 'rehype-raw';
|
|
724
|
+
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
|
725
|
+
import materialDark from 'react-syntax-highlighter/dist/esm/styles/prism/material-dark.js';
|
|
726
|
+
import materialLight from 'react-syntax-highlighter/dist/esm/styles/prism/material-light.js';
|
|
727
|
+
import { Button, ScrollArea, Tooltip } from './components/ui';
|
|
728
|
+
import './AIChatPanel.css';
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
**Missing:**
|
|
732
|
+
- `ReactDOMServer` - Required for HTML export
|
|
733
|
+
- `EmailModal` component
|
|
734
|
+
- `ToolInfoModal` component
|
|
735
|
+
- `PrismStyle` type import
|
|
736
|
+
|
|
737
|
+
---
|
|
738
|
+
|
|
739
|
+
## 9. Implementation Priority
|
|
740
|
+
|
|
741
|
+
### 🔴 CRITICAL (P0) - Core Features
|
|
742
|
+
1. **EmailModal & ToolInfoModal components** - Required for email/tool features
|
|
743
|
+
2. **Save to HTML** - Frequently used feature
|
|
744
|
+
3. **Email Sharing** - Core collaboration feature
|
|
745
|
+
4. **Tool Approval UI** - Essential for MCP tools
|
|
746
|
+
5. **Customer Email Capture** - Required for some deployments
|
|
747
|
+
|
|
748
|
+
### 🟡 HIGH (P1) - Enhanced Functionality
|
|
749
|
+
6. **Call-to-Action System** - Used by marketing/sales agents
|
|
750
|
+
7. **Progressive Actions** - Better UX for streaming responses
|
|
751
|
+
8. **Dynamic CSS Injection** - Custom branding support
|
|
752
|
+
|
|
753
|
+
### 🟢 MEDIUM (P2) - UI/Polish
|
|
754
|
+
9. **Width/Height props** - Layout flexibility
|
|
755
|
+
10. **scrollToEnd prop** - Scroll behavior control
|
|
756
|
+
11. **prismStyle prop** - Code highlighting customization
|
|
757
|
+
12. **markdownClass prop** - Markdown styling flexibility
|
|
758
|
+
|
|
759
|
+
### 🔵 LOW (P3) - Legacy/Optional
|
|
760
|
+
13. **messages prop** - Pre-populate history (not actively used)
|
|
761
|
+
14. **cssUrl via string** - Alternative to URL-based CSS
|
|
762
|
+
|
|
763
|
+
---
|
|
764
|
+
|
|
765
|
+
## 10. Testing Checklist
|
|
766
|
+
|
|
767
|
+
For each ported feature, verify:
|
|
768
|
+
|
|
769
|
+
- [ ] Props are properly typed in interface
|
|
770
|
+
- [ ] Default values match ChatPanel
|
|
771
|
+
- [ ] State management is correct
|
|
772
|
+
- [ ] Event handlers work correctly
|
|
773
|
+
- [ ] API calls use correct endpoints
|
|
774
|
+
- [ ] Visual styling matches (or improves upon) ChatPanel
|
|
775
|
+
- [ ] Accessibility is maintained
|
|
776
|
+
- [ ] Error handling is robust
|
|
777
|
+
- [ ] Loading states are clear
|
|
778
|
+
- [ ] Mobile responsiveness (if applicable)
|
|
779
|
+
|
|
780
|
+
---
|
|
781
|
+
|
|
782
|
+
## 11. Notes & Gotchas
|
|
783
|
+
|
|
784
|
+
### Action Processing Complexity
|
|
785
|
+
ChatPanel's `processActionsWithContext` function (lines 761-939) is **significantly more complex** than AIChatPanel's `processActions` (lines 966-983). Key differences:
|
|
786
|
+
- Context-aware processing (history vs streaming)
|
|
787
|
+
- Progressive action support
|
|
788
|
+
- Button state management during streaming
|
|
789
|
+
- Stable ID registry to prevent duplicates
|
|
790
|
+
|
|
791
|
+
### Email Validation
|
|
792
|
+
ChatPanel uses regex: `/^[^\s@]+@[^\s@]+\.[^\s@]+$/` (line 142)
|
|
793
|
+
Currently duplicated in multiple places - should be a shared utility.
|
|
794
|
+
|
|
795
|
+
### API Endpoints
|
|
796
|
+
All email/save/CTA features use: `${publicAPIUrl}/share/email`
|
|
797
|
+
Feedback tracking uses: `${publicAPIUrl}/feedback/{callId}/{action}`
|
|
798
|
+
|
|
799
|
+
### CSS Modularity
|
|
800
|
+
ChatPanel uses a single CSS file (`ChatPanel.css`) for both the main panel and modals.
|
|
801
|
+
Consider whether AIChatPanel should:
|
|
802
|
+
- Import modal CSS separately
|
|
803
|
+
- Keep everything in `AIChatPanel.css`
|
|
804
|
+
- Use CSS modules
|
|
805
|
+
|
|
806
|
+
---
|
|
807
|
+
|
|
808
|
+
## 12. Success Criteria
|
|
809
|
+
|
|
810
|
+
Port is complete when:
|
|
811
|
+
1. ✅ All props from ChatPanel exist in AIChatPanel
|
|
812
|
+
2. ✅ All components (EmailModal, ToolInfoModal) are integrated
|
|
813
|
+
3. ✅ All features (save, email, CTA, tool approval) work correctly
|
|
814
|
+
4. ✅ All state variables and handlers are ported
|
|
815
|
+
5. ✅ CSS classes are available and styled
|
|
816
|
+
6. ✅ No regressions in existing AIChatPanel features
|
|
817
|
+
7. ✅ All tests pass (create tests if none exist)
|
|
818
|
+
8. ✅ Documentation is updated
|
|
819
|
+
|
|
820
|
+
---
|
|
821
|
+
|
|
822
|
+
## 13. Version History
|
|
823
|
+
|
|
824
|
+
| Date | Version | Changes |
|
|
825
|
+
|------|---------|---------|
|
|
826
|
+
| Dec 15, 2025 | 1.0 | Initial comprehensive inventory |
|
|
827
|
+
|
|
828
|
+
---
|
|
829
|
+
|
|
830
|
+
**End of Inventory**
|