@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.
@@ -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**