@admin-layout/tailwind-chat-pro 12.2.4-alpha.81 → 12.2.4-alpha.82

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.
Files changed (31) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/lib/components/messages-container-ui/ErrorFixCard.d.ts +10 -0
  3. package/lib/components/messages-container-ui/ErrorFixCard.d.ts.map +1 -0
  4. package/lib/components/messages-container-ui/ErrorFixCard.js +219 -0
  5. package/lib/components/messages-container-ui/ErrorFixCard.js.map +1 -0
  6. package/lib/components/messages-container-ui/FilesList.d.ts +19 -0
  7. package/lib/components/messages-container-ui/FilesList.d.ts.map +1 -0
  8. package/lib/components/messages-container-ui/FilesList.js +75 -0
  9. package/lib/components/messages-container-ui/FilesList.js.map +1 -0
  10. package/lib/components/messages-container-ui/ModernMessageGroup.d.ts +46 -0
  11. package/lib/components/messages-container-ui/ModernMessageGroup.d.ts.map +1 -0
  12. package/lib/components/messages-container-ui/ModernMessageGroup.js +1883 -0
  13. package/lib/components/messages-container-ui/ModernMessageGroup.js.map +1 -0
  14. package/lib/components/messages-container-ui/index.d.ts +3 -0
  15. package/lib/components/messages-container-ui/index.d.ts.map +1 -1
  16. package/lib/components/messages-container-ui/messenger-slot-fill-name-enum.d.ts +14 -0
  17. package/lib/components/messages-container-ui/messenger-slot-fill-name-enum.d.ts.map +1 -0
  18. package/lib/components/messages-container-ui/messenger-slot-fill-name-enum.js +14 -0
  19. package/lib/components/messages-container-ui/messenger-slot-fill-name-enum.js.map +1 -0
  20. package/lib/components/messages-container-ui/modernStreaming.d.ts +11 -0
  21. package/lib/components/messages-container-ui/modernStreaming.d.ts.map +1 -0
  22. package/lib/components/messages-container-ui/modernStreaming.js +17 -0
  23. package/lib/components/messages-container-ui/modernStreaming.js.map +1 -0
  24. package/lib/index.js +1 -1
  25. package/package.json +11 -5
  26. package/src/components/messages-container-ui/ErrorFixCard.tsx +239 -0
  27. package/src/components/messages-container-ui/FilesList.tsx +89 -0
  28. package/src/components/messages-container-ui/ModernMessageGroup.tsx +2224 -0
  29. package/src/components/messages-container-ui/index.ts +10 -0
  30. package/src/components/messages-container-ui/messenger-slot-fill-name-enum.ts +13 -0
  31. package/src/components/messages-container-ui/modernStreaming.tsx +18 -0
@@ -0,0 +1,1883 @@
1
+ import {jsx,jsxs,Fragment}from'react/jsx-runtime';import {Slot}from'@common-stack/components-pro';import {AiAgentMessageRole}from'common';import {format,differenceInMinutes}from'date-fns';import {marked}from'marked';import React,{useMemo,useEffect}from'react';import ReactMarkdown from'react-markdown';import remarkBreaks from'remark-breaks';import remarkGfm from'remark-gfm';import {MessengerSlotFillNameEnum}from'./messenger-slot-fill-name-enum.js';import {FilesList}from'./FilesList.js';import {ErrorFixCard}from'./ErrorFixCard.js';// Configure marked for better markdown rendering
2
+ marked.setOptions({
3
+ breaks: true,
4
+ // Convert line breaks to <br>
5
+ gfm: true,
6
+ // GitHub Flavored Markdown
7
+ headerIds: false,
8
+ // Disable header IDs for cleaner HTML
9
+ mangle: false // Don't mangle email addresses
10
+ });
11
+ const ASSISTANT_RESPONSE_THEME_CLASS = 'llm-response-theme';
12
+ const ASSISTANT_RESPONSE_CLASS = 'llm-response';
13
+ const ASSISTANT_RESPONSE_BUBBLE_CLASS = 'llm-response__bubble';
14
+ const ASSISTANT_RESPONSE_MARKDOWN_CLASS = 'llm-response__markdown';
15
+ const USER_MESSAGE_TEXT_SIZE_CLASS = 'text-base';
16
+ const USER_MESSAGE_PROSE_SIZE_CLASS = 'prose-base';
17
+ function getAssistantResponseClasses(innerClassName) {
18
+ return [ASSISTANT_RESPONSE_MARKDOWN_CLASS, innerClassName].filter(Boolean).join(' ');
19
+ }
20
+ function AssistantResponseWrapper({
21
+ children,
22
+ bubbleClassName
23
+ }) {
24
+ return jsx("div", {
25
+ className: ASSISTANT_RESPONSE_THEME_CLASS,
26
+ children: jsx("div", {
27
+ className: ASSISTANT_RESPONSE_CLASS,
28
+ children: jsx("div", {
29
+ className: [ASSISTANT_RESPONSE_BUBBLE_CLASS, bubbleClassName].filter(Boolean).join(' '),
30
+ children: children
31
+ })
32
+ })
33
+ });
34
+ }
35
+ // Enhanced CSS styles for HTML content rendering with prettification
36
+ const htmlContentStyles = `
37
+ .html-content {
38
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
39
+ line-height: 1.6;
40
+ color: #374151;
41
+ }
42
+
43
+ .html-content h1, .html-content h2, .html-content h3, .html-content h4, .html-content h5, .html-content h6 {
44
+ margin-top: 1.5rem;
45
+ margin-bottom: 1rem;
46
+ font-weight: 600;
47
+ line-height: 1.25;
48
+ color: #111827;
49
+ }
50
+
51
+ .html-content h1 {
52
+ font-size: 1.875rem;
53
+ border-bottom: 2px solid #e5e7eb;
54
+ padding-bottom: 0.5rem;
55
+ }
56
+ .${ASSISTANT_RESPONSE_THEME_CLASS} {
57
+ --llm-content-max-width: 100%;
58
+ --llm-font-sans: 'Avenir Next', 'Segoe UI', sans-serif;
59
+ --llm-font-mono: 'IBM Plex Mono', 'SFMono-Regular', Menlo, Consolas, monospace;
60
+ --llm-color-bg: #ffffff;
61
+ --llm-color-surface: #f6f7f8;
62
+ --llm-color-surface-2: #eef1f4;
63
+ --llm-color-surface-hover: #e9edf2;
64
+ --llm-color-border: #e3e7ee;
65
+ --llm-color-border-strong: #cfd7e3;
66
+ --llm-color-text: #121926;
67
+ --llm-color-text-secondary: #667085;
68
+ --llm-color-marker: #8e98a8;
69
+ --llm-color-link-underline: rgba(18, 25, 38, 0.18);
70
+ --llm-color-code-bg: rgba(249, 115, 22, 0.1);
71
+ --llm-color-code-text: #d96b00;
72
+ --llm-color-code-block-border: #ebeef3;
73
+ --llm-shadow-soft: 0 1px 2px rgba(18, 25, 38, 0.04), 0 10px 32px rgba(18, 25, 38, 0.05);
74
+ --llm-radius-bubble: 1.5rem;
75
+ --llm-radius-code: 0.85rem;
76
+ --llm-line-height-body: 1.62;
77
+ --llm-line-height-mobile: 1.55;
78
+ --llm-line-height-heading: 1.35;
79
+ --llm-space-heading-top: 1.45rem;
80
+ --llm-space-heading-bottom: 0.75rem;
81
+ --llm-space-paragraph: 1rem;
82
+ --llm-space-list: 0.9rem;
83
+ color: var(--llm-color-text);
84
+ background: transparent;
85
+ }
86
+
87
+ .${ASSISTANT_RESPONSE_THEME_CLASS}[data-color-mode='dark'],
88
+ [data-color-mode='dark'] .${ASSISTANT_RESPONSE_THEME_CLASS} {
89
+ --llm-color-bg: #0f1115;
90
+ --llm-color-surface: #171a20;
91
+ --llm-color-surface-2: #1f2530;
92
+ --llm-color-surface-hover: #232a36;
93
+ --llm-color-border: #2a3240;
94
+ --llm-color-border-strong: #364052;
95
+ --llm-color-text: #f3f5f8;
96
+ --llm-color-text-secondary: #aab3c2;
97
+ --llm-color-marker: #7f8999;
98
+ --llm-color-link-underline: rgba(243, 245, 248, 0.24);
99
+ --llm-color-code-bg: rgba(251, 146, 60, 0.14);
100
+ --llm-color-code-text: #ffb36b;
101
+ --llm-color-code-block-border: #262d39;
102
+ --llm-shadow-soft: 0 1px 2px rgba(0, 0, 0, 0.28), 0 14px 36px rgba(0, 0, 0, 0.3);
103
+ }
104
+
105
+ .${ASSISTANT_RESPONSE_CLASS} {
106
+ position: relative;
107
+ display: flex;
108
+ flex-direction: column;
109
+ align-items: flex-start;
110
+ justify-content: center;
111
+ width: 100%;
112
+ max-width: var(--llm-content-max-width);
113
+ font-family: var(--llm-font-sans);
114
+ color: var(--llm-color-text);
115
+ }
116
+
117
+ .${ASSISTANT_RESPONSE_BUBBLE_CLASS} {
118
+ position: relative;
119
+ width: 100%;
120
+ min-height: 1.75rem;
121
+ border-radius: var(--llm-radius-bubble);
122
+ color: inherit;
123
+ word-break: break-word;
124
+ }
125
+
126
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} {
127
+ position: relative;
128
+ width: 100%;
129
+ max-width: none;
130
+ color: inherit;
131
+ font-size: 1rem;
132
+ line-height: var(--llm-line-height-body);
133
+ }
134
+
135
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} > :first-child {
136
+ margin-top: 0;
137
+ }
138
+
139
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} > :last-child {
140
+ margin-bottom: 0;
141
+ }
142
+
143
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} p,
144
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} li,
145
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} th,
146
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} td,
147
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} blockquote,
148
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS}.html-content,
149
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS}.assistant-html-content {
150
+ color: inherit;
151
+ }
152
+
153
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} p,
154
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS}.html-content p,
155
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS}.assistant-html-content p {
156
+ margin: 0 0 var(--llm-space-paragraph);
157
+ white-space: pre-wrap;
158
+ }
159
+
160
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} h1,
161
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} h2,
162
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} h3,
163
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} h4,
164
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} h5,
165
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} h6 {
166
+ margin: var(--llm-space-heading-top) 0 var(--llm-space-heading-bottom);
167
+ font-weight: 600;
168
+ letter-spacing: -0.02em;
169
+ line-height: var(--llm-line-height-heading);
170
+ color: var(--llm-color-text);
171
+ }
172
+
173
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} h1 { font-size: 1.75rem; }
174
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} h2 { font-size: 1.5rem; }
175
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} h3 { font-size: 1.25rem; }
176
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} h4 { font-size: 1.05rem; }
177
+
178
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} strong,
179
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} b {
180
+ font-weight: 600;
181
+ color: var(--llm-color-text);
182
+ }
183
+
184
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} em,
185
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} i {
186
+ color: var(--llm-color-text-secondary);
187
+ }
188
+
189
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} a {
190
+ color: currentColor;
191
+ text-decoration: underline;
192
+ text-decoration-color: var(--llm-color-link-underline);
193
+ text-underline-offset: 0.18em;
194
+ transition: color 120ms ease, text-decoration-color 120ms ease;
195
+ }
196
+
197
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} a:hover {
198
+ color: var(--llm-color-text);
199
+ text-decoration-color: currentColor;
200
+ }
201
+
202
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} ul,
203
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} ol {
204
+ margin: 0.35rem 0 var(--llm-space-list);
205
+ padding-left: 1.15rem;
206
+ }
207
+
208
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} ul {
209
+ list-style-type: disc;
210
+ }
211
+
212
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} ol {
213
+ list-style-type: decimal;
214
+ /* Wider gutter so multi-digit markers (e.g. "60.") aren't clipped at the
215
+ content's left edge — long numbered content (verse, step lists) showed
216
+ "0." for line 60. */
217
+ padding-left: 2rem;
218
+ }
219
+
220
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} li {
221
+ margin: 0.28rem 0;
222
+ padding-inline-start: 0.1rem;
223
+ /* \`normal\` collapses the stray "\\n" react-markdown inserts between <li>
224
+ and its child <p>; with \`pre-wrap\` that newline rendered as a blank
225
+ first line, stranding the marker above its text. Intra-paragraph
226
+ whitespace is preserved by the \`li > p\` rule below. */
227
+ white-space: normal;
228
+ }
229
+
230
+ /* Loose list items wrap their text in <p>. Keep pre-wrap for the text but
231
+ drop the paragraph margins so each item is a single tight line — fixes
232
+ poems / verse the model emits as numbered lists rendering as a sprawling,
233
+ double-spaced listicle. */
234
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} li > p {
235
+ margin-top: 0;
236
+ white-space: pre-wrap;
237
+ }
238
+
239
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} li > p:last-child {
240
+ margin-bottom: 0;
241
+ }
242
+
243
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} li > ul,
244
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} li > ol {
245
+ margin-top: 0.35rem;
246
+ margin-bottom: 0.15rem;
247
+ white-space: normal;
248
+ }
249
+
250
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} ::marker {
251
+ color: var(--llm-color-marker);
252
+ }
253
+
254
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} hr {
255
+ margin: 1.25rem 0;
256
+ border: 0;
257
+ border-top: 1px solid var(--llm-color-border);
258
+ }
259
+
260
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} blockquote {
261
+ margin: 1rem 0;
262
+ padding: 0.2rem 0 0.2rem 1rem;
263
+ border-left: 2px solid var(--llm-color-border-strong);
264
+ background: transparent;
265
+ color: var(--llm-color-text-secondary);
266
+ }
267
+
268
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} blockquote p {
269
+ margin-bottom: 0;
270
+ color: inherit;
271
+ }
272
+
273
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} :not(pre) > code,
274
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} .llm-inline-code {
275
+ display: inline-block;
276
+ padding: 0.08rem 0.38rem;
277
+ border: 0;
278
+ border-radius: 0.32rem;
279
+ font-family: var(--llm-font-mono);
280
+ font-size: 0.875em;
281
+ line-height: 1.45;
282
+ background: var(--llm-color-code-bg);
283
+ color: var(--llm-color-code-text);
284
+ box-decoration-break: clone;
285
+ -webkit-box-decoration-break: clone;
286
+ }
287
+
288
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} table {
289
+ width: 100%;
290
+ margin: 1rem 0 1.1rem;
291
+ border-collapse: separate;
292
+ border-spacing: 0;
293
+ border: 1px solid var(--llm-color-border);
294
+ border-radius: 1rem;
295
+ overflow: hidden;
296
+ box-shadow: var(--llm-shadow-soft);
297
+ }
298
+
299
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} thead {
300
+ background: var(--llm-color-surface);
301
+ }
302
+
303
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} th,
304
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} td {
305
+ padding: 0.85rem 1rem;
306
+ border-bottom: 1px solid var(--llm-color-border);
307
+ text-align: left;
308
+ vertical-align: top;
309
+ }
310
+
311
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} th {
312
+ font-size: 0.8rem;
313
+ font-weight: 600;
314
+ letter-spacing: 0.02em;
315
+ color: var(--llm-color-text-secondary);
316
+ background: var(--llm-color-surface);
317
+ }
318
+
319
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} td {
320
+ color: var(--llm-color-text);
321
+ background: var(--llm-color-bg);
322
+ }
323
+
324
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} tbody tr:last-child td {
325
+ border-bottom: 0;
326
+ }
327
+
328
+ @media (max-width: 768px) {
329
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} {
330
+ font-size: 0.95rem;
331
+ line-height: var(--llm-line-height-mobile);
332
+ }
333
+
334
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} th,
335
+ .${ASSISTANT_RESPONSE_MARKDOWN_CLASS} td {
336
+ padding: 0.75rem 0.8rem;
337
+ }
338
+ }
339
+ * we must neutralize it so it doesn't override the pill background/padding.
340
+ */
341
+ .user-message .message-container {
342
+ background: transparent !important;
343
+ padding-top: 0 !important;
344
+ padding-bottom: 0 !important;
345
+ margin: 0 !important;
346
+ transition: none !important;
347
+ box-shadow: none !important;
348
+ }
349
+
350
+ .assistant-message-container {
351
+ background: transparent;
352
+ padding-top: 0;
353
+ padding-bottom: 0;
354
+ margin: 0;
355
+ transition: none;
356
+ box-shadow: none;
357
+ }
358
+
359
+ .assistant-html-content {
360
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
361
+ line-height: 1.6;
362
+ color: #374151;
363
+ }
364
+
365
+ .assistant-html-content h1,
366
+ .assistant-html-content h2,
367
+ .assistant-html-content h3,
368
+ .assistant-html-content h4,
369
+ .assistant-html-content h5,
370
+ .assistant-html-content h6 {
371
+ font-weight: 600;
372
+ line-height: 1.25;
373
+ color: #111827;
374
+ }
375
+
376
+ .assistant-html-content h1 {
377
+ font-size: 1.875rem;
378
+ margin-top: 2rem;
379
+ margin-bottom: 1rem;
380
+ }
381
+
382
+ .assistant-html-content h2 {
383
+ font-size: 1.5rem;
384
+ margin-top: 2rem;
385
+ margin-bottom: 1rem;
386
+ }
387
+
388
+ .assistant-html-content h3 {
389
+ font-size: 1.25rem;
390
+ margin-top: 1.5rem;
391
+ margin-bottom: 1rem;
392
+ }
393
+
394
+ .assistant-html-content h4 {
395
+ font-size: 1.125rem;
396
+ margin-top: 1rem;
397
+ margin-bottom: 0.5rem;
398
+ }
399
+
400
+ .assistant-html-content p {
401
+ margin-top: 0;
402
+ margin-bottom: 1rem;
403
+ line-height: 1.625;
404
+ color: #374151;
405
+ }
406
+
407
+ .assistant-html-content ul,
408
+ .assistant-html-content ol {
409
+ margin: 1rem 0;
410
+ padding-left: 1.25rem;
411
+ }
412
+
413
+ .assistant-html-content ul {
414
+ list-style-type: disc;
415
+ }
416
+
417
+ .assistant-html-content ol {
418
+ list-style-type: decimal;
419
+ }
420
+
421
+ .assistant-html-content li {
422
+ margin-bottom: 0.375rem;
423
+ line-height: 1.625;
424
+ color: #374151;
425
+ }
426
+
427
+ .assistant-html-content li > ul,
428
+ .assistant-html-content li > ol {
429
+ margin-top: 0.375rem;
430
+ margin-bottom: 0.375rem;
431
+ }
432
+
433
+ .assistant-html-content a {
434
+ color: #2563eb;
435
+ text-decoration: none;
436
+ }
437
+
438
+ .assistant-html-content a:hover {
439
+ color: #1d4ed8;
440
+ text-decoration: underline;
441
+ text-underline-offset: 2px;
442
+ }
443
+
444
+ .assistant-html-content strong,
445
+ .assistant-html-content b {
446
+ font-weight: 600;
447
+ color: #111827;
448
+ }
449
+
450
+ .assistant-html-content em,
451
+ .assistant-html-content i {
452
+ font-style: italic;
453
+ color: #4b5563;
454
+ }
455
+
456
+ .assistant-html-content code {
457
+ background-color: #f3f4f6;
458
+ padding: 0.25rem 0.5rem;
459
+ border-radius: 0.375rem;
460
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
461
+ font-size: 0.875rem;
462
+ color: #dc2626;
463
+ border: 1px solid #e5e7eb;
464
+ }
465
+
466
+ .assistant-html-content pre {
467
+ background-color: #f9fafb;
468
+ padding: 1rem;
469
+ border-radius: 0.5rem;
470
+ overflow-x: auto;
471
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
472
+ font-size: 0.875rem;
473
+ line-height: 1.5;
474
+ border: 1px solid #e5e7eb;
475
+ margin: 1.5rem 0;
476
+ }
477
+
478
+ .assistant-html-content pre code {
479
+ background-color: transparent;
480
+ padding: 0;
481
+ border: none;
482
+ color: #374151;
483
+ font-size: inherit;
484
+ }
485
+
486
+ .assistant-html-content blockquote {
487
+ margin: 1.5rem 0;
488
+ padding: 0 0 0 1rem;
489
+ border-left: 4px solid #d1d5db;
490
+ font-style: italic;
491
+ color: #6b7280;
492
+ background-color: transparent;
493
+ border-radius: 0;
494
+ }
495
+
496
+ .assistant-html-content blockquote p {
497
+ margin-bottom: 0;
498
+ }
499
+
500
+ .assistant-html-content table {
501
+ border-collapse: collapse;
502
+ border: 1px solid #e5e7eb;
503
+ width: 100%;
504
+ margin: 1rem 0;
505
+ }
506
+
507
+ .assistant-html-content th,
508
+ .assistant-html-content td {
509
+ text-align: left;
510
+ vertical-align: top;
511
+ }
512
+
513
+ .assistant-html-content th {
514
+ padding: 0.5rem 1rem;
515
+ background-color: #f9fafb;
516
+ border: 1px solid #e5e7eb;
517
+ font-weight: 500;
518
+ font-size: 0.75rem;
519
+ letter-spacing: 0.04em;
520
+ text-transform: uppercase;
521
+ color: #4b5563;
522
+ }
523
+
524
+ .assistant-html-content td {
525
+ padding: 0.5rem 1rem;
526
+ border-top: 1px solid #e5e7eb;
527
+ color: #374151;
528
+ }
529
+
530
+ .assistant-html-content hr {
531
+ border: none;
532
+ border-top: 1px solid #e5e7eb;
533
+ margin: 2rem 0;
534
+ }
535
+
536
+ /* Markdown content styling */
537
+ .markdown-content {
538
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
539
+ line-height: 1.6;
540
+ color: #374151;
541
+ }
542
+
543
+ .markdown-content h1 {
544
+ font-size: 1.5rem;
545
+ font-weight: 700;
546
+ margin-top: 1.5rem;
547
+ margin-bottom: 1rem;
548
+ color: #111827;
549
+ padding-bottom: 0.5rem;
550
+ border-bottom: 2px solid #e5e7eb;
551
+ }
552
+
553
+ .markdown-content h2 {
554
+ font-size: 1.25rem;
555
+ font-weight: 600;
556
+ margin-top: 1.25rem;
557
+ margin-bottom: 0.75rem;
558
+ color: #1f2937;
559
+ padding-bottom: 0.375rem;
560
+ border-bottom: 1px solid #e5e7eb;
561
+ }
562
+
563
+ .markdown-content h3 {
564
+ font-size: 1.125rem;
565
+ font-weight: 600;
566
+ margin-top: 1rem;
567
+ margin-bottom: 0.5rem;
568
+ color: #374151;
569
+ }
570
+
571
+ .markdown-content p {
572
+ margin-bottom: 0.75rem;
573
+ line-height: 1.6;
574
+ color: #4b5563;
575
+ }
576
+
577
+ .markdown-content ul, .markdown-content ol {
578
+ margin: 1rem 0;
579
+ padding-left: 1.5rem;
580
+ }
581
+
582
+ .markdown-content li {
583
+ margin-bottom: 0.5rem;
584
+ line-height: 1.6;
585
+ }
586
+
587
+ .markdown-content code {
588
+ background-color: #f3f4f6;
589
+ padding: 0.125rem 0.375rem;
590
+ border-radius: 0.25rem;
591
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
592
+ font-size: 0.875rem;
593
+ color: #dc2626;
594
+ border: 1px solid #e5e7eb;
595
+ }
596
+
597
+ .markdown-content pre {
598
+ color: #1f2937;
599
+ border-radius: 8px;
600
+ overflow-x: auto;
601
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
602
+ font-size: 0.875rem;
603
+ line-height: 1.5;
604
+ margin: 1rem 0;
605
+ }
606
+
607
+ .markdown-content pre code {
608
+ background-color: transparent;
609
+ padding: 0;
610
+ border: none;
611
+ color: #1f2937;
612
+ font-size: inherit;
613
+ }
614
+
615
+ .markdown-content blockquote {
616
+ border-left: 4px solid #3b82f6;
617
+ background-color: #f8fafc;
618
+ padding: 1rem;
619
+ margin: 1rem 0;
620
+ border-radius: 6px;
621
+ font-style: italic;
622
+ color: #475569;
623
+ border-top: 1px solid #e5e7eb;
624
+ border-right: 1px solid #e5e7eb;
625
+ border-bottom: 1px solid #e5e7eb;
626
+ }
627
+
628
+ .markdown-content table {
629
+ border-collapse: collapse;
630
+ border: 1px solid #e5e7eb;
631
+ width: 100%;
632
+ margin: 1rem 0;
633
+ border-radius: 8px;
634
+ overflow: hidden;
635
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
636
+ }
637
+
638
+ .markdown-content th {
639
+ background-color: #f9fafb;
640
+ color: #374151;
641
+ font-weight: 600;
642
+ padding: 0.75rem;
643
+ text-align: left;
644
+ border-bottom: 1px solid #e5e7eb;
645
+ font-size: 0.875rem;
646
+ }
647
+
648
+ .markdown-content td {
649
+ border: 1px solid #e5e7eb;
650
+ padding: 0.75rem;
651
+ text-align: left;
652
+ vertical-align: top;
653
+ background-color: white;
654
+ font-size: 0.875rem;
655
+ }
656
+
657
+ .markdown-content tr:nth-child(even) td {
658
+ background-color: #f9fafb;
659
+ }
660
+
661
+ .markdown-content tr:hover td {
662
+ background-color: #f3f4f6;
663
+ }
664
+
665
+ /* Enhanced list styling */
666
+ .markdown-content ul li {
667
+ position: relative;
668
+ padding-left: 0.5rem;
669
+ }
670
+
671
+ .markdown-content ul li::before {
672
+ content: "•";
673
+ color: #000;
674
+ font-weight: bold;
675
+ position: absolute;
676
+ left: -1rem;
677
+ }
678
+
679
+ .markdown-content ol li {
680
+ position: relative;
681
+ padding-left: 0.5rem;
682
+ }
683
+
684
+ .markdown-content ol li::before {
685
+ content: counter(list-item) ".";
686
+ font-weight: 600;
687
+ position: absolute;
688
+ left: -1.5rem;
689
+ }
690
+
691
+ /* Code block container styling */
692
+ .code-block-container {
693
+ background: #ffffff;
694
+ border: 1px solid #e5e7eb;
695
+ border-radius: 8px;
696
+ overflow: hidden;
697
+ margin: 1rem 0;
698
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
699
+ }
700
+
701
+ .code-block-header {
702
+ background: #f9fafb;
703
+ padding: 0.5rem 1rem;
704
+ border-bottom: 1px solid #e5e7eb;
705
+ font-weight: 600;
706
+ color: #374151;
707
+ font-size: 0.875rem;
708
+ text-transform: uppercase;
709
+ letter-spacing: 0.05em;
710
+ }
711
+
712
+ .code-block-content {
713
+ padding: 1rem;
714
+ overflow-x: auto;
715
+ }
716
+
717
+ /* Enhanced prose styling for markdown content */
718
+ .prose pre {
719
+ background-color: #f9fafb;
720
+ border: 1px solid #e5e7eb;
721
+ border-radius: 0.5rem;
722
+ padding: 1rem;
723
+ overflow-x: auto;
724
+ }
725
+
726
+ .prose code {
727
+ background-color: #f3f4f6;
728
+ padding: 0.125rem 0.375rem;
729
+ border-radius: 0.25rem;
730
+ font-size: 0.875rem;
731
+ border: 1px solid #e5e7eb;
732
+ }
733
+
734
+ .prose pre code {
735
+ background-color: transparent;
736
+ padding: 0;
737
+ border: none;
738
+ color:#000;
739
+ }
740
+
741
+ .hover\:bg-gray-100:hover {
742
+ background-color: transparent !important;
743
+ }
744
+ `;
745
+ // Hook to inject CSS styles
746
+ const useInjectStyles = () => {
747
+ useEffect(() => {
748
+ const styleElementPresent = !!document.getElementById('html-content-styles');
749
+ // Check if styles are already injected
750
+ if (styleElementPresent) {
751
+ return;
752
+ }
753
+ // Create and inject style element
754
+ const styleElement = document.createElement('style');
755
+ styleElement.id = 'html-content-styles';
756
+ styleElement.textContent = htmlContentStyles;
757
+ document.head.appendChild(styleElement);
758
+ // Cleanup function
759
+ return () => {
760
+ const existingStyle = document.getElementById('html-content-styles');
761
+ if (existingStyle) {
762
+ existingStyle.remove();
763
+ }
764
+ };
765
+ }, []);
766
+ };
767
+ // Enhanced utility function to group messages by user and time
768
+ const groupMessagesByUserAndTime = (messages, timeThresholdMinutes = 5) => {
769
+ if (!messages || messages.length === 0) return [];
770
+ const groups = [];
771
+ let currentGroup = [];
772
+ let lastMessage = null;
773
+ for (const message of messages) {
774
+ if (typeof message === 'string') continue; // Skip date separators
775
+ const shouldStartNewGroup = !lastMessage || lastMessage.author?.id !== message.author?.id || differenceInMinutes(new Date(message.createdAt), new Date(lastMessage.createdAt)) > timeThresholdMinutes;
776
+ if (shouldStartNewGroup) {
777
+ if (currentGroup.length > 0) {
778
+ groups.push(currentGroup);
779
+ }
780
+ currentGroup = [message];
781
+ } else {
782
+ currentGroup.push(message);
783
+ }
784
+ lastMessage = message;
785
+ }
786
+ if (currentGroup.length > 0) {
787
+ groups.push(currentGroup);
788
+ }
789
+ return groups;
790
+ };
791
+ // Enhanced content detection that properly identifies different content types
792
+ const detectContentType = value => {
793
+ if (!value) return 'text';
794
+ const trimmed = value.trim();
795
+ // Check for markdown patterns FIRST (before code, as markdown can contain code blocks)
796
+ const markdownPatterns = [/^#{1,6}\s+.+$/gm,
797
+ // Headers (must have text after #)
798
+ /^\s*[-*+]\s+.+$/gm,
799
+ // Unordered lists
800
+ /^\s*\d+\.\s+.+$/gm,
801
+ // Numbered lists
802
+ /\[.+?\]\(.+?\)/g,
803
+ // Links
804
+ /!\[.+?\]\(.+?\)/g,
805
+ // Images
806
+ /```[\s\S]*?```/g,
807
+ // Fenced code blocks (markdown feature)
808
+ /^\s*>\s+/gm,
809
+ // Blockquotes
810
+ /\*\*.*?\*\*/g,
811
+ // Bold
812
+ /\*[^*].*?\*/g,
813
+ // Italic (not just asterisks)
814
+ /^\s*\|.+\|/gm // Tables
815
+ ];
816
+ // Count markdown patterns - if we have multiple, it's likely markdown
817
+ const markdownMatches = markdownPatterns.filter(pattern => {
818
+ const matches = trimmed.match(pattern);
819
+ return matches && matches.length > 0;
820
+ }).length;
821
+ // If we have 2+ markdown patterns, it's definitely markdown
822
+ if (markdownMatches >= 2) return 'markdown';
823
+ // If we have headers or lists, it's likely markdown
824
+ if (/^#{1,6}\s+/gm.test(trimmed) || /^\s*[-*+]\s+/gm.test(trimmed) || /^\s*\d+\.\s+/gm.test(trimmed)) {
825
+ return 'markdown';
826
+ }
827
+ // Check for pure code patterns (without markdown context)
828
+ const pureCodePatterns = [/^---\s*\w+\.\w+\s*---$/gm,
829
+ // File separators (standalone)
830
+ /^@tailwind\s+\w+/gm,
831
+ // Tailwind directives at start
832
+ /^export\s+default\s*\{/gm,
833
+ // JS/TS exports at start
834
+ /^import\s+.*\s+from\s+['"]/gm // Import statements at start
835
+ ];
836
+ const hasPureCodePatterns = pureCodePatterns.some(pattern => pattern.test(trimmed));
837
+ if (hasPureCodePatterns && markdownMatches === 0) return 'code';
838
+ // Check for HTML patterns
839
+ const htmlPatterns = [/<[a-z][\s\S]*?>/i,
840
+ // HTML tags
841
+ /&[a-z0-9#]+;/i,
842
+ // HTML entities
843
+ /<[^>]+>[^<]*<\/[^>]+>/i // HTML structure
844
+ ];
845
+ const hasHtmlPatterns = htmlPatterns.some(pattern => pattern.test(trimmed));
846
+ // Only return HTML if it's clearly HTML and not markdown
847
+ if (hasHtmlPatterns && markdownMatches === 0) return 'html';
848
+ // Default to text, but FormattedMessageContent will still try to parse as markdown
849
+ return 'text';
850
+ };
851
+ /** Map message type (THINKING, TOOLUSE, etc.) to slot fill name for Kimi-like streaming UI */
852
+ const getMessageSlotName = type => {
853
+ if (!type) return null;
854
+ const t = String(type).toUpperCase();
855
+ if (t === 'THINKING') return MessengerSlotFillNameEnum.THINKING_NAME;
856
+ if (t === 'TOOLUSE') return MessengerSlotFillNameEnum.TOOLUSE_NAME;
857
+ if (t === 'CODEBLOCK') return MessengerSlotFillNameEnum.CODEBLOCK_NAME;
858
+ if (t === 'SERVICE') return MessengerSlotFillNameEnum.SERVICE_NAME;
859
+ if (t === 'AIASSISTANT' || t === 'ASSISTANT') return MessengerSlotFillNameEnum.AIASSISTANT_NAME;
860
+ if (t === 'TRAVEL') return MessengerSlotFillNameEnum.TRAVEL_NAME;
861
+ if (t === 'MEDIA') return MessengerSlotFillNameEnum.MEDIA_NAME;
862
+ if (t === 'WORKFLOW') return MessengerSlotFillNameEnum.WORKFLOW_NAME;
863
+ return null;
864
+ };
865
+ // Enhanced sanitizer that allows most HTML tags while maintaining security
866
+ const sanitizeHtml = html => {
867
+ try {
868
+ if (typeof window === 'undefined' || typeof window.DOMParser === 'undefined') {
869
+ return html.replace(/<script[\s\S]*?>[\s\S]*?<\/script>/gi, '').replace(/<style[\s\S]*?>[\s\S]*?<\/style>/gi, '').replace(/on\w+\s*=\s*"[^"]*"/gi, '').replace(/on\w+\s*=\s*'[^']*'/gi, '').replace(/on\w+\s*=\s*[^\s>]+/gi, '').replace(/javascript:/gi, '').replace(/data:text\/html/gi, '');
870
+ }
871
+ const parser = new window.DOMParser();
872
+ const doc = parser.parseFromString(html, 'text/html');
873
+ // Only remove the most dangerous tags
874
+ const dangerousTags = ['script', 'style'];
875
+ dangerousTags.forEach(tag => {
876
+ doc.querySelectorAll(tag).forEach(el => el.remove());
877
+ });
878
+ const walker = doc.createTreeWalker(doc.body, NodeFilter.SHOW_ELEMENT);
879
+ let node = walker.nextNode();
880
+ while (node) {
881
+ Array.from(node.attributes).forEach(attr => {
882
+ const name = attr.name.toLowerCase();
883
+ const value = (attr.value || '').toLowerCase();
884
+ // Remove event handlers and javascript protocols
885
+ if (name.startsWith('on') || value.startsWith('javascript:')) {
886
+ node?.removeAttribute(attr.name);
887
+ }
888
+ // Allow most attributes but validate URLs for src/href
889
+ if ((name === 'src' || name === 'href') && !/^https?:|^\/.*/.test(attr.value)) {
890
+ node?.removeAttribute(attr.name);
891
+ }
892
+ });
893
+ node = walker.nextNode();
894
+ }
895
+ return doc.body.innerHTML;
896
+ } catch (e) {
897
+ return html.replace(/<script[\s\S]*?>[\s\S]*?<\/script>/gi, '');
898
+ }
899
+ };
900
+ // Function to prettify HTML content
901
+ const prettifyHtmlContent = htmlContent => {
902
+ // Add semantic classes for better styling
903
+ let prettified = htmlContent;
904
+ // Enhance headings with better styling
905
+ prettified = prettified.replace(/<h([1-6])>/g, '<h$1 class="html-heading html-heading-$1">');
906
+ // Enhance lists with better styling
907
+ prettified = prettified.replace(/<ul>/g, '<ul class="html-list html-list-unordered">');
908
+ prettified = prettified.replace(/<ol>/g, '<ol class="html-list html-list-ordered">');
909
+ // Enhance paragraphs with better spacing
910
+ prettified = prettified.replace(/<p>/g, '<p class="html-paragraph">');
911
+ // Enhance code blocks
912
+ prettified = prettified.replace(/<code>/g, '<code class="html-inline-code">');
913
+ prettified = prettified.replace(/<pre>/g, '<pre class="html-code-block">');
914
+ // Enhance blockquotes
915
+ prettified = prettified.replace(/<blockquote>/g, '<blockquote class="html-blockquote">');
916
+ // Enhance tables
917
+ prettified = prettified.replace(/<table>/g, '<table class="html-table">');
918
+ prettified = prettified.replace(/<th>/g, '<th class="html-table-header">');
919
+ prettified = prettified.replace(/<td>/g, '<td class="html-table-cell">');
920
+ // Add info boxes for certain content patterns
921
+ prettified = prettified.replace(/<p>(Note|Tip|Info|Warning|Error):\s*(.*?)<\/p>/gi, '<div class="html-info-box"><strong>$1:</strong> $2</div>');
922
+ return prettified;
923
+ };
924
+ // Minimal Builder-like blocks renderer (text/image/button/columns)
925
+ const BuilderLikeRenderer = ({
926
+ blocks
927
+ }) => {
928
+ if (!blocks || !Array.isArray(blocks) || blocks.length === 0) return null;
929
+ return jsx("div", {
930
+ className: "space-y-3",
931
+ children: blocks.map((block, idx) => {
932
+ const type = block?.['@type'] || block?.type;
933
+ if (!type) return null;
934
+ if (/text/i.test(type)) {
935
+ const html = sanitizeHtml(block?.text || block?.data?.text || '');
936
+ return jsx("div", {
937
+ className: "max-w-none text-gray-800",
938
+ dangerouslySetInnerHTML: {
939
+ __html: html
940
+ }
941
+ }, idx);
942
+ }
943
+ if (/image/i.test(type)) {
944
+ const src = block?.src || block?.image || block?.data?.src;
945
+ const alt = block?.alt || block?.data?.alt || '';
946
+ if (!src) return null;
947
+ return jsx("div", {
948
+ className: "rounded-lg overflow-hidden border border-gray-200",
949
+ children: jsx("img", {
950
+ className: "w-full h-auto",
951
+ src: src,
952
+ alt: alt
953
+ })
954
+ }, idx);
955
+ }
956
+ if (/button/i.test(type)) {
957
+ const text = block?.text || block?.data?.text || 'Button';
958
+ const href = block?.href || block?.link || block?.data?.href || '#';
959
+ return jsx("a", {
960
+ href: href,
961
+ target: "_blank",
962
+ rel: "noopener noreferrer",
963
+ className: "inline-flex items-center px-3 py-1.5 rounded-md bg-blue-600 text-white text-sm hover:bg-blue-700",
964
+ children: text
965
+ }, idx);
966
+ }
967
+ if (/columns?/i.test(type)) {
968
+ const cols = block?.columns || block?.data?.columns || [];
969
+ return jsx("div", {
970
+ className: "grid grid-cols-1 sm:grid-cols-2 gap-3",
971
+ children: cols.map((col, colIdx) => jsx("div", {
972
+ className: "space-y-2",
973
+ children: jsx(BuilderLikeRenderer, {
974
+ blocks: col?.blocks || col?.children
975
+ })
976
+ }, colIdx))
977
+ }, idx);
978
+ }
979
+ return jsx("div", {
980
+ className: "message-container",
981
+ children: jsx("pre", {
982
+ className: "text-xs text-gray-600 overflow-x-auto",
983
+ children: JSON.stringify(block, null, 2)
984
+ })
985
+ }, idx);
986
+ })
987
+ });
988
+ };
989
+ // Code block with optional header (language + copy) and footer (copy + download) per Image 2
990
+ const CodeBlockWithActions = ({
991
+ content,
992
+ language,
993
+ showCopyDownload = true
994
+ }) => {
995
+ const [copied, setCopied] = React.useState(false);
996
+ const handleCopy = React.useCallback(() => {
997
+ navigator.clipboard.writeText(content).then(() => {
998
+ setCopied(true);
999
+ setTimeout(() => setCopied(false), 2000);
1000
+ });
1001
+ }, [content]);
1002
+ const handleDownload = React.useCallback(() => {
1003
+ const ext = language === 'jsx' || language === 'tsx' ? '.tsx' : language === 'js' || language === 'ts' ? '.ts' : '.txt';
1004
+ const blob = new Blob([content], {
1005
+ type: 'text/plain'
1006
+ });
1007
+ const url = URL.createObjectURL(blob);
1008
+ const a = document.createElement('a');
1009
+ a.href = url;
1010
+ a.download = `snippet${ext}`;
1011
+ a.click();
1012
+ URL.revokeObjectURL(url);
1013
+ }, [content, language]);
1014
+ const langLabel = (language || 'code').toUpperCase();
1015
+ /**
1016
+ * Duplicate / copy: back square offset top-left (only its top-left shows behind),
1017
+ * front square on top toward bottom-right — matches typical “two sheets” reference art.
1018
+ */
1019
+ const IconCopy = ({
1020
+ className
1021
+ }) => jsxs("svg", {
1022
+ className: className,
1023
+ viewBox: "0 0 24 24",
1024
+ fill: "none",
1025
+ xmlns: "http://www.w3.org/2000/svg",
1026
+ "aria-hidden": true,
1027
+ children: [jsx("rect", {
1028
+ x: "3.25",
1029
+ y: "3.25",
1030
+ width: "10.5",
1031
+ height: "10.5",
1032
+ rx: "2.25",
1033
+ ry: "2.25",
1034
+ stroke: "currentColor",
1035
+ strokeWidth: "1.5",
1036
+ strokeLinecap: "round",
1037
+ strokeLinejoin: "round"
1038
+ }), jsx("rect", {
1039
+ x: "8.75",
1040
+ y: "8.75",
1041
+ width: "10.5",
1042
+ height: "10.5",
1043
+ rx: "2.25",
1044
+ ry: "2.25",
1045
+ stroke: "currentColor",
1046
+ strokeWidth: "1.5",
1047
+ strokeLinecap: "round",
1048
+ strokeLinejoin: "round"
1049
+ })]
1050
+ });
1051
+ /** Arrow into tray (download) */
1052
+ const IconDownload = ({
1053
+ className
1054
+ }) => jsx("svg", {
1055
+ className: className,
1056
+ viewBox: "0 0 24 24",
1057
+ fill: "none",
1058
+ stroke: "currentColor",
1059
+ strokeWidth: 1.5,
1060
+ strokeLinecap: "round",
1061
+ strokeLinejoin: "round",
1062
+ "aria-hidden": true,
1063
+ children: jsx("path", {
1064
+ d: "M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3"
1065
+ })
1066
+ });
1067
+ const IconCheck = ({
1068
+ className
1069
+ }) => jsx("svg", {
1070
+ className: className,
1071
+ viewBox: "0 0 24 24",
1072
+ fill: "none",
1073
+ stroke: "currentColor",
1074
+ strokeWidth: 1.75,
1075
+ strokeLinecap: "round",
1076
+ strokeLinejoin: "round",
1077
+ "aria-hidden": true,
1078
+ children: jsx("path", {
1079
+ d: "M5 13l4 4L19 7"
1080
+ })
1081
+ });
1082
+ const iconMuted = 'text-slate-500 hover:text-slate-600';
1083
+ return jsxs("div", {
1084
+ className: "rounded-xl bg-white overflow-hidden my-3 shadow-sm ring-1 ring-slate-200/60",
1085
+ children: [jsxs("div", {
1086
+ className: "flex items-center justify-between px-4 pt-4 pb-3 bg-white",
1087
+ children: [jsx("span", {
1088
+ className: "text-[11px] font-semibold uppercase tracking-wide text-slate-500",
1089
+ children: langLabel
1090
+ }), jsx("button", {
1091
+ type: "button",
1092
+ onClick: handleCopy,
1093
+ className: `p-1 rounded-md hover:bg-slate-100/80 transition-colors ${iconMuted}`,
1094
+ title: "Copy",
1095
+ "aria-label": "Copy to clipboard",
1096
+ children: jsx(IconCopy, {
1097
+ className: "w-[15px] h-[15px] shrink-0"
1098
+ })
1099
+ })]
1100
+ }), jsx("div", {
1101
+ className: "px-4 pb-4",
1102
+ children: jsx("div", {
1103
+ className: "overflow-x-auto bg-white py-4",
1104
+ children: jsx("pre", {
1105
+ className: "text-sm text-slate-900 font-mono leading-relaxed whitespace-pre-wrap break-words m-0 p-0 border-0 bg-transparent",
1106
+ children: jsx("code", {
1107
+ className: "bg-transparent",
1108
+ children: content
1109
+ })
1110
+ })
1111
+ })
1112
+ }), showCopyDownload && jsxs("div", {
1113
+ className: "flex items-center justify-start gap-0.5 px-4 pb-3 pt-0 mt-2 bg-white",
1114
+ children: [jsx("button", {
1115
+ type: "button",
1116
+ onClick: handleCopy,
1117
+ className: `p-1 rounded-md hover:bg-slate-100/80 transition-colors ${iconMuted}`,
1118
+ title: copied ? 'Copied' : 'Copy',
1119
+ "aria-label": copied ? 'Copied' : 'Copy to clipboard',
1120
+ children: copied ? jsx(IconCheck, {
1121
+ className: "w-[15px] h-[15px] text-emerald-600"
1122
+ }) : jsx(IconCopy, {
1123
+ className: "w-[15px] h-[15px] shrink-0"
1124
+ })
1125
+ }), jsx("button", {
1126
+ type: "button",
1127
+ onClick: handleDownload,
1128
+ className: `p-1 rounded-md hover:bg-slate-100/80 transition-colors ${iconMuted}`,
1129
+ title: "Download",
1130
+ "aria-label": "Download",
1131
+ children: jsx(IconDownload, {
1132
+ className: "w-[15px] h-[15px] shrink-0"
1133
+ })
1134
+ })]
1135
+ })]
1136
+ });
1137
+ };
1138
+ // Infer language from file extension or content
1139
+ const inferCodeLanguage = (section, filename) => {
1140
+ if (/\bimport\b.*from\s+['"]react['"]/i.test(section) || /<\w+[\s>]/.test(section)) return 'jsx';
1141
+ if (/\bimport\b/.test(section) && /\.(ts|tsx)\b/.test(section)) return 'ts';
1142
+ return 'code';
1143
+ };
1144
+ // Enhanced code formatter for raw code content
1145
+ const CodeFormatter = ({
1146
+ content,
1147
+ showCopyDownload = true,
1148
+ variant = 'default',
1149
+ planMode = false
1150
+ }) => {
1151
+ if (!content) return null;
1152
+ // Split content by file separators and format each section
1153
+ const sections = content.split(/(---\s*\w+\.\w+\s*---)/g);
1154
+ return jsx("div", {
1155
+ className: variant === 'user' ? '' : 'message-container',
1156
+ children: jsx("div", {
1157
+ className: "space-y-4",
1158
+ children: sections.map((section, index) => {
1159
+ if (!section.trim()) return null;
1160
+ // Check if this is a file separator
1161
+ const fileMatch = section.match(/---\s*(\w+\.\w+)\s*---/);
1162
+ if (fileMatch) {
1163
+ return jsx("div", {
1164
+ className: "code-block-container",
1165
+ children: jsxs("div", {
1166
+ className: "code-block-header",
1167
+ children: ["\uD83D\uDCC4 ", fileMatch[1]]
1168
+ })
1169
+ }, index);
1170
+ }
1171
+ // Format the code content
1172
+ const lines = section.trim().split('\n');
1173
+ const isCode = lines.some(line => line.includes('@tailwind') || line.includes('export default') || line.includes('import ') || line.includes('{') || line.includes('}') || line.includes('//') || line.includes('/*'));
1174
+ if (isCode) {
1175
+ const lang = inferCodeLanguage(section);
1176
+ return jsx(CodeBlockWithActions, {
1177
+ content: section.trim(),
1178
+ language: lang,
1179
+ showCopyDownload: showCopyDownload
1180
+ }, index);
1181
+ }
1182
+ // Regular text content
1183
+ return jsx("div", {
1184
+ className: `${variant === 'user' ? USER_MESSAGE_TEXT_SIZE_CLASS : 'text-sm'} ${variant === 'user' ? planMode ? 'text-primary-foreground' : 'text-gray-900' : 'text-gray-700'} leading-relaxed whitespace-pre-wrap break-words`,
1185
+ children: section.trim()
1186
+ }, index);
1187
+ })
1188
+ })
1189
+ });
1190
+ };
1191
+ // Enhanced markdown renderer using marked (similar to MessageBubble.tsx)
1192
+ const FormattedMessageContent = ({
1193
+ content,
1194
+ variant = 'default',
1195
+ planMode = false,
1196
+ showCopyDownloadOnCodeBlock = false
1197
+ }) => {
1198
+ if (!content) return null;
1199
+ // Parse markdown using marked - be more aggressive in detection
1200
+ const {
1201
+ htmlContent,
1202
+ markdownSource
1203
+ } = useMemo(() => {
1204
+ try {
1205
+ const trimmed = content.trim();
1206
+ // Comprehensive markdown detection patterns
1207
+ const markdownIndicators = [/^#{1,6}\s+.+$/gm,
1208
+ // Headers with text
1209
+ /^\s*[-*+]\s+.+$/gm,
1210
+ // Unordered lists
1211
+ /^\s*\d+\.\s+.+$/gm,
1212
+ // Numbered lists
1213
+ /\[.+?\]\(.+?\)/g,
1214
+ // Links
1215
+ /!\[.+?\]\(.+?\)/g,
1216
+ // Images
1217
+ /```[\s\S]*?```/g,
1218
+ // Fenced code blocks
1219
+ /^\s*>\s+/gm,
1220
+ // Blockquotes
1221
+ /\*\*[^*]+\*\*/g,
1222
+ // Bold text
1223
+ /\*[^*\n]+\*/g,
1224
+ // Italic text (not just asterisks)
1225
+ /^\s*\|.+\|/gm,
1226
+ // Tables
1227
+ /~~.+~~/g // Strikethrough
1228
+ ];
1229
+ // Check if content has markdown patterns
1230
+ const hasMarkdown = markdownIndicators.some(pattern => {
1231
+ const matches = trimmed.match(pattern);
1232
+ return matches && matches.length > 0;
1233
+ });
1234
+ // Also check for common markdown characters that suggest markdown content
1235
+ const hasMarkdownChars = /[#*`>|\[\]()]/.test(trimmed) && trimmed.length > 10;
1236
+ // If we detect markdown, parse it
1237
+ if (hasMarkdown || hasMarkdownChars) {
1238
+ const parsed = marked.parse(trimmed, {
1239
+ async: false
1240
+ });
1241
+ // Only return if parsing actually produced HTML (not just the same text)
1242
+ if (parsed && parsed.trim() !== trimmed && parsed.includes('<')) {
1243
+ return {
1244
+ htmlContent: parsed,
1245
+ markdownSource: trimmed
1246
+ };
1247
+ }
1248
+ }
1249
+ return {
1250
+ htmlContent: null,
1251
+ markdownSource: null
1252
+ };
1253
+ } catch (error) {
1254
+ console.error('Error parsing markdown:', error);
1255
+ return {
1256
+ htmlContent: null,
1257
+ markdownSource: null
1258
+ };
1259
+ }
1260
+ }, [content]);
1261
+ const userMarkdownClasses = `${USER_MESSAGE_TEXT_SIZE_CLASS} prose ${USER_MESSAGE_PROSE_SIZE_CLASS} max-w-none leading-relaxed break-words
1262
+ prose-p:my-2 prose-p:text-gray-700
1263
+ prose-pre:my-2 prose-pre:bg-transparent prose-pre:border-0 prose-pre:p-0
1264
+ prose-ul:my-2 prose-ol:my-2
1265
+ prose-code:break-words prose-code:bg-gray-50 prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded prose-code:text-sm prose-code:font-mono
1266
+ prose-headings:font-semibold prose-headings:text-gray-900 prose-headings:mt-4 prose-headings:mb-2
1267
+ prose-h1:text-2xl prose-h1:border-b prose-h1:border-gray-200 prose-h1:pb-2
1268
+ prose-h2:text-xl prose-h2:border-b prose-h2:border-gray-200 prose-h2:pb-1
1269
+ prose-h3:text-lg
1270
+ prose-h4:text-base
1271
+ prose-a:text-blue-600 prose-a:no-underline hover:prose-a:underline
1272
+ prose-strong:font-semibold prose-strong:text-gray-900
1273
+ prose-em:text-gray-600
1274
+ prose-blockquote:border-l-4 prose-blockquote:border-blue-400 prose-blockquote:pl-4 prose-blockquote:py-2 prose-blockquote:bg-blue-50 prose-blockquote:rounded-r-md prose-blockquote:my-3 prose-blockquote:italic
1275
+ prose-table:w-full prose-table:border prose-table:border-gray-200 prose-table:rounded-md prose-table:my-4
1276
+ prose-th:px-3 prose-th:py-2 prose-th:text-left prose-th:font-semibold prose-th:bg-gray-50 prose-th:border prose-th:border-gray-200
1277
+ prose-td:px-3 prose-td:py-2 prose-td:border prose-td:border-gray-200 prose-td:text-gray-700
1278
+ prose-li:my-1 prose-li:text-gray-700
1279
+ prose-hr:my-4 prose-hr:border-gray-200
1280
+ ${planMode ? '!prose-p:text-primary-foreground !prose-li:text-primary-foreground !prose-strong:text-primary-foreground !prose-em:text-primary-foreground !prose-a:text-primary-foreground' : '!prose-p:text-gray-900 !prose-li:text-gray-900 !prose-strong:text-gray-900 !prose-em:text-gray-700 !prose-a:text-blue-600'}`;
1281
+ const userHtmlClasses = `${USER_MESSAGE_TEXT_SIZE_CLASS} prose ${USER_MESSAGE_PROSE_SIZE_CLASS} max-w-none leading-relaxed break-words
1282
+ prose-p:my-2 prose-p:text-gray-700
1283
+ prose-pre:my-2 prose-pre:bg-gray-50 prose-pre:border prose-pre:border-gray-200 prose-pre:rounded prose-pre:p-3
1284
+ prose-ul:my-2 prose-ol:my-2
1285
+ prose-pre:whitespace-pre-wrap prose-pre:break-words prose-pre:overflow-x-auto
1286
+ prose-code:break-words prose-code:bg-gray-50 prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded prose-code:text-sm prose-code:font-mono
1287
+ prose-headings:font-semibold prose-headings:text-gray-900 prose-headings:mt-4 prose-headings:mb-2
1288
+ prose-h1:text-2xl prose-h1:border-b prose-h1:border-gray-200 prose-h1:pb-2
1289
+ prose-h2:text-xl prose-h2:border-b prose-h2:border-gray-200 prose-h2:pb-1
1290
+ prose-h3:text-lg
1291
+ prose-h4:text-base
1292
+ prose-a:text-blue-600 prose-a:no-underline hover:prose-a:underline
1293
+ prose-strong:font-semibold prose-strong:text-gray-900
1294
+ prose-em:text-gray-600
1295
+ prose-blockquote:border-l-4 prose-blockquote:border-blue-400 prose-blockquote:pl-4 prose-blockquote:py-2 prose-blockquote:bg-blue-50 prose-blockquote:rounded-r-md prose-blockquote:my-3 prose-blockquote:italic
1296
+ prose-table:w-full prose-table:border prose-table:border-gray-200 prose-table:rounded-md prose-table:my-4
1297
+ prose-th:px-3 prose-th:py-2 prose-th:text-left prose-th:font-semibold prose-th:bg-gray-50 prose-th:border prose-th:border-gray-200
1298
+ prose-td:px-3 prose-td:py-2 prose-td:border prose-td:border-gray-200 prose-td:text-gray-700
1299
+ prose-li:my-1 prose-li:text-gray-700
1300
+ prose-hr:my-4 prose-hr:border-gray-200
1301
+ ${planMode ? '!prose-p:text-primary-foreground !prose-li:text-primary-foreground !prose-strong:text-primary-foreground !prose-em:text-primary-foreground !prose-a:text-primary-foreground' : '!prose-p:text-gray-900 !prose-li:text-gray-900 !prose-strong:text-gray-900 !prose-em:text-gray-700 !prose-a:text-blue-600'}`;
1302
+ const markdownComponents = useMemo(() => {
1303
+ const isUserVariant = variant === 'user';
1304
+ const userTextClass = planMode ? 'text-primary-foreground' : 'text-gray-900';
1305
+ const userMutedTextClass = planMode ? 'text-primary-foreground' : 'text-gray-700';
1306
+ const userLinkClass = planMode ? 'text-primary-foreground underline underline-offset-2' : 'text-blue-600 hover:underline underline-offset-2';
1307
+ return {
1308
+ p: ({
1309
+ children
1310
+ }) => isUserVariant ? jsx("p", {
1311
+ className: `break-words mb-4 last:mb-0 leading-relaxed ${userTextClass}`,
1312
+ children: children
1313
+ }) : jsx("p", {
1314
+ children: children
1315
+ }),
1316
+ h1: ({
1317
+ children
1318
+ }) => isUserVariant ? jsx("h1", {
1319
+ className: `text-2xl font-semibold mt-6 mb-3 first:mt-0 ${userTextClass}`,
1320
+ children: children
1321
+ }) : jsx("h1", {
1322
+ children: children
1323
+ }),
1324
+ h2: ({
1325
+ children
1326
+ }) => isUserVariant ? jsx("h2", {
1327
+ className: `text-xl font-semibold mt-5 mb-3 first:mt-0 ${userTextClass}`,
1328
+ children: children
1329
+ }) : jsx("h2", {
1330
+ children: children
1331
+ }),
1332
+ h3: ({
1333
+ children
1334
+ }) => isUserVariant ? jsx("h3", {
1335
+ className: `text-lg font-semibold mt-4 mb-2 first:mt-0 ${userTextClass}`,
1336
+ children: children
1337
+ }) : jsx("h3", {
1338
+ children: children
1339
+ }),
1340
+ h4: ({
1341
+ children
1342
+ }) => isUserVariant ? jsx("h4", {
1343
+ className: `text-base font-semibold mt-4 mb-2 first:mt-0 ${userTextClass}`,
1344
+ children: children
1345
+ }) : jsx("h4", {
1346
+ children: children
1347
+ }),
1348
+ blockquote: ({
1349
+ children
1350
+ }) => isUserVariant ? jsx("blockquote", {
1351
+ className: `border-l-4 border-blue-400 pl-4 py-2 bg-blue-50 rounded-r-md my-3 italic ${userMutedTextClass}`,
1352
+ children: children
1353
+ }) : jsx("blockquote", {
1354
+ children: children
1355
+ }),
1356
+ hr: () => isUserVariant ? jsx("hr", {
1357
+ className: "my-4 border-gray-200"
1358
+ }) : jsx("hr", {}),
1359
+ ul: ({
1360
+ children
1361
+ }) => jsx("ul", {
1362
+ className: isUserVariant ? `list-disc pl-5 mb-4 space-y-1.5 marker:text-gray-500 [&_ul]:mt-1.5 [&_ol]:mt-1.5 ${userTextClass}` : 'list-disc pl-5',
1363
+ children: children
1364
+ }),
1365
+ ol: ({
1366
+ children
1367
+ }) => jsx("ol", {
1368
+ className: isUserVariant ? `list-decimal pl-5 mb-4 space-y-1.5 marker:text-gray-500 [&_ul]:mt-1.5 [&_ol]:mt-1.5 ${userTextClass}` : 'list-decimal pl-5',
1369
+ children: children
1370
+ }),
1371
+ li: ({
1372
+ children
1373
+ }) => jsx("li", {
1374
+ className: isUserVariant ? `break-words whitespace-pre-wrap leading-relaxed [&>ul]:whitespace-normal [&>ol]:whitespace-normal ${userMutedTextClass}` : 'break-words whitespace-pre-wrap leading-relaxed [&>ul]:whitespace-normal [&>ol]:whitespace-normal',
1375
+ children: children
1376
+ }),
1377
+ a: ({
1378
+ href,
1379
+ children
1380
+ }) => isUserVariant ? jsx("a", {
1381
+ href: href,
1382
+ className: userLinkClass,
1383
+ children: children
1384
+ }) : jsx("a", {
1385
+ href: href,
1386
+ children: children
1387
+ }),
1388
+ table: ({
1389
+ children,
1390
+ ...props
1391
+ }) => isUserVariant ? jsx("div", {
1392
+ className: "overflow-x-auto my-4 w-full rounded-md border border-gray-200",
1393
+ children: jsx("table", {
1394
+ className: "w-full text-sm",
1395
+ ...props,
1396
+ children: children
1397
+ })
1398
+ }) : jsx("div", {
1399
+ className: "overflow-x-auto my-4 w-full",
1400
+ children: jsx("table", {
1401
+ ...props,
1402
+ children: children
1403
+ })
1404
+ }),
1405
+ thead: ({
1406
+ children
1407
+ }) => isUserVariant ? jsx("thead", {
1408
+ className: "bg-gray-50 text-left",
1409
+ children: children
1410
+ }) : jsx("thead", {
1411
+ children: children
1412
+ }),
1413
+ th: ({
1414
+ children
1415
+ }) => isUserVariant ? jsx("th", {
1416
+ className: `px-3 py-2 text-left font-semibold ${userTextClass}`,
1417
+ children: children
1418
+ }) : jsx("th", {
1419
+ children: children
1420
+ }),
1421
+ td: ({
1422
+ children
1423
+ }) => isUserVariant ? jsx("td", {
1424
+ className: `px-3 py-2 ${userMutedTextClass}`,
1425
+ children: children
1426
+ }) : jsx("td", {
1427
+ children: children
1428
+ }),
1429
+ pre: ({
1430
+ children
1431
+ }) => jsx(Fragment, {
1432
+ children: children
1433
+ }),
1434
+ code: ({
1435
+ className,
1436
+ children,
1437
+ ...rest
1438
+ }) => {
1439
+ const match = /language-(\w+)/.exec(className || '');
1440
+ const text = String(children).replace(/\n$/, '');
1441
+ if (match) {
1442
+ return jsx("div", {
1443
+ className: "not-prose max-w-none",
1444
+ children: jsx(CodeBlockWithActions, {
1445
+ content: text,
1446
+ language: match[1],
1447
+ showCopyDownload: showCopyDownloadOnCodeBlock
1448
+ })
1449
+ });
1450
+ }
1451
+ if (text.includes('\n')) {
1452
+ return jsx("div", {
1453
+ className: "not-prose max-w-none",
1454
+ children: jsx(CodeBlockWithActions, {
1455
+ content: text,
1456
+ language: "code",
1457
+ showCopyDownload: showCopyDownloadOnCodeBlock
1458
+ })
1459
+ });
1460
+ }
1461
+ return jsx("code", {
1462
+ className: isUserVariant ? `rounded px-1.5 py-0.5 text-sm font-mono bg-gray-50 break-words ${className || ''}` : `llm-inline-code break-words ${className || ''}`,
1463
+ ...rest,
1464
+ children: children
1465
+ });
1466
+ }
1467
+ };
1468
+ }, [planMode, showCopyDownloadOnCodeBlock, variant]);
1469
+ // Fenced code copy/download needs React tree (not marked HTML)
1470
+ if (showCopyDownloadOnCodeBlock && markdownSource) {
1471
+ if (variant === 'user') {
1472
+ return jsx("div", {
1473
+ className: userMarkdownClasses,
1474
+ children: jsx(ReactMarkdown, {
1475
+ remarkPlugins: [remarkGfm, remarkBreaks],
1476
+ components: markdownComponents,
1477
+ children: markdownSource
1478
+ })
1479
+ });
1480
+ }
1481
+ return jsx(AssistantResponseWrapper, {
1482
+ children: jsx("div", {
1483
+ className: getAssistantResponseClasses('min-w-0'),
1484
+ children: jsx(ReactMarkdown, {
1485
+ remarkPlugins: [remarkGfm, remarkBreaks],
1486
+ components: markdownComponents,
1487
+ children: markdownSource
1488
+ })
1489
+ })
1490
+ });
1491
+ }
1492
+ // If markdown was parsed, render as HTML with prose styling
1493
+ if (htmlContent) {
1494
+ if (variant === 'user') {
1495
+ return jsx("div", {
1496
+ className: userHtmlClasses,
1497
+ dangerouslySetInnerHTML: {
1498
+ __html: htmlContent
1499
+ }
1500
+ });
1501
+ }
1502
+ return jsx(AssistantResponseWrapper, {
1503
+ children: jsx("div", {
1504
+ className: getAssistantResponseClasses('min-w-0'),
1505
+ dangerouslySetInnerHTML: {
1506
+ __html: htmlContent
1507
+ }
1508
+ })
1509
+ });
1510
+ }
1511
+ // For plain text content
1512
+ return variant === 'user' ? jsx("p", {
1513
+ className: `${USER_MESSAGE_TEXT_SIZE_CLASS} leading-relaxed whitespace-pre-wrap break-words ${planMode ? 'text-primary-foreground' : 'text-gray-900'}`,
1514
+ children: content
1515
+ }) : jsx(AssistantResponseWrapper, {
1516
+ children: jsx("div", {
1517
+ className: getAssistantResponseClasses(),
1518
+ children: jsx("p", {
1519
+ className: "leading-relaxed whitespace-pre-wrap break-words",
1520
+ children: content
1521
+ })
1522
+ })
1523
+ });
1524
+ };
1525
+ // Detect predominant text direction (RTL/LTR) for international content
1526
+ const detectTextDirection = text => {
1527
+ if (!text) return 'ltr';
1528
+ const rtlRanges = /[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]/g; // Hebrew, Arabic, etc.
1529
+ const ltrRanges = /[A-Za-z\u00C0-\u024F\u1E00-\u1EFF]/g;
1530
+ const rtlCount = (text.match(rtlRanges) || []).length;
1531
+ const ltrCount = (text.match(ltrRanges) || []).length;
1532
+ return rtlCount > ltrCount ? 'rtl' : 'ltr';
1533
+ };
1534
+ // Lightweight language heuristic for lang attribute
1535
+ const detectLanguageTag = text => {
1536
+ if (!text) return 'en';
1537
+ if (/\p{Script=Arabic}/u.test(text)) return 'ar';
1538
+ if (/[\u0590-\u05FF]/.test(text)) return 'he';
1539
+ if (/[\u0600-\u06FF]/.test(text)) return 'fa';
1540
+ if (/[\u0900-\u097F]/.test(text)) return 'hi';
1541
+ if (/[\u4E00-\u9FFF]/.test(text)) return 'zh';
1542
+ if (/[\u3040-\u309F\u30A0-\u30FF]/.test(text)) return 'ja';
1543
+ if (/[\uAC00-\uD7AF]/.test(text)) return 'ko';
1544
+ if (/[\u0400-\u04FF]/.test(text)) return 'ru';
1545
+ if (/[\u0E00-\u0E7F]/.test(text)) return 'th';
1546
+ return 'en';
1547
+ };
1548
+ // Attempt to parse Builder.io-like JSON blocks embedded as message string
1549
+ const tryExtractBuilderBlocks = content => {
1550
+ if (!content || content.length < 2) return null;
1551
+ try {
1552
+ const trimmed = content.trim();
1553
+ const jsonMatch = trimmed.match(/([\[{][\s\S]*[\]}])/);
1554
+ const jsonText = jsonMatch ? jsonMatch[1] : trimmed;
1555
+ const parsed = JSON.parse(jsonText);
1556
+ if (Array.isArray(parsed)) {
1557
+ if (parsed.length && (parsed[0]['@type'] || parsed[0].type)) return parsed;
1558
+ } else if (parsed && typeof parsed === 'object') {
1559
+ if (Array.isArray(parsed.blocks)) return parsed.blocks;
1560
+ if (Array.isArray(parsed?.data?.blocks)) return parsed.data.blocks;
1561
+ }
1562
+ return null;
1563
+ } catch {
1564
+ return null;
1565
+ }
1566
+ };
1567
+ const ModernMessageGroup = ({
1568
+ author,
1569
+ messages,
1570
+ currentUser,
1571
+ showAuthorName = false,
1572
+ showAvatar = false,
1573
+ showTimestamp = false,
1574
+ onOpen,
1575
+ onMessageClick,
1576
+ isDesktopView = false,
1577
+ isSmallScreen = false,
1578
+ showCopyDownloadOnCodeBlock = false
1579
+ }) => {
1580
+ // Inject CSS styles for HTML content
1581
+ useInjectStyles();
1582
+ // Preserve server / store order for posts (TOOLUSE, THINKING, etc.); no client-side reordering
1583
+ const displayMessages = messages;
1584
+ //const isOwnMessage = author?.id === currentUser?.id;
1585
+ //const isOwnMessage = messages.some((message: any) => (message as any)?.propsConfiguration?.contents?.role === AiAgentMessageRole.User);
1586
+ const isOwnMessage = displayMessages.some(message => message?.propsConfiguration?.contents?.role === AiAgentMessageRole.User) || Array.isArray(author?.alias) && typeof currentUser?.authUserId === 'string' && author.alias.some(alias => alias?.toLowerCase() === currentUser.authUserId.toLowerCase());
1587
+ author?.givenName && author?.familyName ? `${author.givenName} ${author.familyName}` : author?.username || 'Unknown User';
1588
+ displayMessages[0];
1589
+ const formatTime = timestamp => {
1590
+ const date = new Date(timestamp);
1591
+ return format(date, 'h:mm a');
1592
+ };
1593
+ // Determine if this is an AI/system message for special styling
1594
+ const isSystemMessage = author?.username?.toLowerCase().includes('ai') || author?.username?.toLowerCase().includes('assistant') || author?.username?.toLowerCase().includes('system');
1595
+ // const isSystemMessage = messages.some(
1596
+ // (message: any) => (message as any)?.propsConfiguration?.contents?.role === AiAgentMessageRole.Assistant,
1597
+ // );
1598
+ // For user messages, don't show group header, just individual messages with avatars
1599
+ if (isOwnMessage) {
1600
+ return jsx("div", {
1601
+ className: `min-h-fit ${isDesktopView ? 'space-y-1 mb-3' : isSmallScreen ? 'space-y-0.5 mb-2' : 'space-y-0.5 mb-2'}`,
1602
+ children: displayMessages.map((message, index) => jsx(ModernMessageBubble, {
1603
+ message: message,
1604
+ isOwnMessage: isOwnMessage,
1605
+ isSystemMessage: isSystemMessage,
1606
+ isFirstInGroup: index === 0,
1607
+ isLastInGroup: index === displayMessages.length - 1,
1608
+ onMessageClick: onMessageClick,
1609
+ formatTime: formatTime,
1610
+ showAuthorName: showAuthorName,
1611
+ showAvatar: showAvatar,
1612
+ showTimestamp: showTimestamp,
1613
+ showCopyDownloadOnCodeBlock: showCopyDownloadOnCodeBlock
1614
+ }, message.id))
1615
+ });
1616
+ }
1617
+ // For other messages (non-user), show the traditional group layout
1618
+ return jsx("div", {
1619
+ className: `group transition-all duration-300 ease-in-out ${isDesktopView ? 'mb-4 px-4 py-3' : isSmallScreen ? 'mb-2 px-2 py-1' : 'mb-3 px-3 py-1'} ${isSystemMessage ? 'bg-white rounded-lg' : ' rounded-lg'}`,
1620
+ children: jsx("div", {
1621
+ className: `flex items-start ${isDesktopView ? 'space-x-4' : isSmallScreen ? 'space-x-2' : 'space-x-3'}`,
1622
+ children: jsx("div", {
1623
+ className: "flex-1 min-w-0 overflow-hidden",
1624
+ children: jsx("div", {
1625
+ className: "space-y-1",
1626
+ children: displayMessages.map((message, index) => jsx(ModernMessageBubble, {
1627
+ message: message,
1628
+ isOwnMessage: isOwnMessage,
1629
+ isSystemMessage: isSystemMessage,
1630
+ isFirstInGroup: index === 0,
1631
+ isLastInGroup: index === displayMessages.length - 1,
1632
+ onMessageClick: onMessageClick,
1633
+ formatTime: formatTime,
1634
+ showTimestamp: showTimestamp,
1635
+ showCopyDownloadOnCodeBlock: showCopyDownloadOnCodeBlock
1636
+ }, message.id))
1637
+ })
1638
+ })
1639
+ })
1640
+ });
1641
+ };
1642
+ const ModernMessageBubble = ({
1643
+ showAuthorName = false,
1644
+ showAvatar = false,
1645
+ showTimestamp = false,
1646
+ message,
1647
+ isOwnMessage,
1648
+ isSystemMessage,
1649
+ isFirstInGroup,
1650
+ isLastInGroup,
1651
+ onMessageClick,
1652
+ formatTime,
1653
+ showCopyDownloadOnCodeBlock = false
1654
+ }) => {
1655
+ const handleClick = () => {
1656
+ onMessageClick?.(message);
1657
+ };
1658
+ const isAssistantRole = message?.propsConfiguration?.contents?.role === AiAgentMessageRole.Assistant;
1659
+ const messageMode = message?.propsConfiguration?.contents?.mode ?? message?.propsConfiguration?.content?.mode ?? message?.props?.mode;
1660
+ const isPlanMode = messageMode === 'plan';
1661
+ // For user messages, create a right-aligned layout with avatar and name
1662
+ if (isOwnMessage) {
1663
+ const authorName = message.author?.givenName && message.author?.familyName ? `${message.author.givenName} ${message.author.familyName}` : message.author?.username || 'You';
1664
+ return jsx("div", {
1665
+ className: "py-1 px-1 sm:px-2 -mx-1 sm:-mx-2 group",
1666
+ children: jsxs("div", {
1667
+ className: "flex items-start justify-end gap-2",
1668
+ children: [jsxs("div", {
1669
+ className: "flex flex-col items-end max-w-[85%]",
1670
+ children: [showAuthorName && jsx("div", {
1671
+ className: "flex items-end space-x-2 mb-0.5",
1672
+ children: jsx("span", {
1673
+ className: "text-sm font-semibold text-gray-900",
1674
+ children: authorName
1675
+ })
1676
+ }), jsxs("div", {
1677
+ className: `${isAssistantRole ? 'text-sm' : USER_MESSAGE_TEXT_SIZE_CLASS} ${isAssistantRole ? 'cursor-pointer rounded-2xl border border-gray-200 bg-gray-50 text-gray-900 px-4 py-2.5' :
1678
+ // : 'user-message rounded-[22px] rounded-br-[4px] px-4 py-3 bg-primary text-primary-foreground shadow-sm'
1679
+ isPlanMode ? 'user-message rounded-[22px] rounded-br-[4px] px-4 py-3 bg-primary text-primary-foreground shadow-sm' : 'user-message rounded-[22px] rounded-br-[4px] px-4 py-3 '}`,
1680
+ onClick: isAssistantRole ? handleClick : undefined,
1681
+ dir: detectTextDirection(message?.message || ''),
1682
+ lang: detectLanguageTag(message?.message || ''),
1683
+ children: [message.message && jsx("div", {
1684
+ className: "max-w-none",
1685
+ children: (() => {
1686
+ const contentType = detectContentType(message.message);
1687
+ if (contentType === 'code') {
1688
+ return jsx(CodeFormatter, {
1689
+ content: message.message,
1690
+ showCopyDownload: showCopyDownloadOnCodeBlock,
1691
+ variant: isAssistantRole ? 'default' : 'user',
1692
+ planMode: !isAssistantRole && isPlanMode
1693
+ });
1694
+ } else if (contentType === 'html') {
1695
+ if (isAssistantRole) {
1696
+ return jsx(AssistantResponseWrapper, {
1697
+ children: jsx("div", {
1698
+ className: getAssistantResponseClasses('min-w-0'),
1699
+ dangerouslySetInnerHTML: {
1700
+ __html: prettifyHtmlContent(sanitizeHtml(message.message))
1701
+ }
1702
+ })
1703
+ });
1704
+ }
1705
+ return jsx("div", {
1706
+ className: isPlanMode ? '!text-primary-foreground html-content' : 'text-gray-900 html-content',
1707
+ dangerouslySetInnerHTML: {
1708
+ __html: prettifyHtmlContent(sanitizeHtml(message.message))
1709
+ }
1710
+ });
1711
+ } else {
1712
+ return jsx(FormattedMessageContent, {
1713
+ content: message.message,
1714
+ variant: isAssistantRole ? 'default' : 'user',
1715
+ planMode: !isAssistantRole && isPlanMode,
1716
+ showCopyDownloadOnCodeBlock: showCopyDownloadOnCodeBlock
1717
+ });
1718
+ }
1719
+ })()
1720
+ }), tryExtractBuilderBlocks(message?.message)?.length ? jsx("div", {
1721
+ className: "mt-2",
1722
+ children: jsx(BuilderLikeRenderer, {
1723
+ blocks: tryExtractBuilderBlocks(message?.message)
1724
+ })
1725
+ }) : null, message?.props?.blocks?.length ? jsx("div", {
1726
+ className: "mt-2",
1727
+ children: jsx(BuilderLikeRenderer, {
1728
+ blocks: message.props.blocks
1729
+ })
1730
+ }) : null, message.files?.totalCount > 0 && jsx("div", {
1731
+ className: "mt-2",
1732
+ children: jsx(FilesList, {
1733
+ uploaded: true,
1734
+ files: message.files.data
1735
+ })
1736
+ }), showTimestamp ? jsx("div", {
1737
+ className: `text-[10px] mt-1 text-right ${isAssistantRole ? 'text-gray-500' : isPlanMode ? 'text-primary-foreground/80' : 'text-gray-500'}`,
1738
+ children: formatTime(message.createdAt)
1739
+ }) : null]
1740
+ })]
1741
+ }), showAvatar && jsx("div", {
1742
+ className: "flex-shrink-0 mt-0.5",
1743
+ children: jsx("img", {
1744
+ className: `w-8 h-8 sm:w-10 sm:h-10 rounded-lg ${isAssistantRole ? '' : 'cursor-pointer hover:opacity-80'} transition-opacity object-cover`,
1745
+ src: message.author?.picture || '/default-avatar.svg',
1746
+ alt: authorName,
1747
+ //onClick={isAssistantRole ? undefined : handleClick}
1748
+ onClick: isAssistantRole ? handleClick : undefined,
1749
+ onError: e => {
1750
+ e.currentTarget.src = '/default-avatar.svg';
1751
+ }
1752
+ })
1753
+ })]
1754
+ })
1755
+ });
1756
+ }
1757
+ // For other messages (left-aligned) — use Slot for streaming types (THINKING, TOOLUSE, etc.) like Kimi
1758
+ const attachment = message?.props?.attachment ?? message?.propsConfiguration?.contents?.attachment;
1759
+ const slotName = getMessageSlotName(message?.type);
1760
+ // Don't show THINKING slot when attachment has no meaningful content or when dismissed
1761
+ const isThinkingSlot = slotName === MessengerSlotFillNameEnum.THINKING_NAME;
1762
+ const shouldShowThinkingSlot = !!attachment && attachment.dismissed !== true && (attachment.isThinking === true || attachment.thought != null && String(attachment.thought).trim() !== '');
1763
+ const shouldShowSlot = isThinkingSlot ? shouldShowThinkingSlot : !!attachment;
1764
+ const hasStreamingSlot = !!slotName && shouldShowSlot;
1765
+ return jsxs("div", {
1766
+ className: `group/message w-full min-w-0 px-0 py-2 ${isSystemMessage || isAssistantRole ? 'cursor-pointer' : ''}`,
1767
+ onClick: isSystemMessage || isAssistantRole ? handleClick : undefined,
1768
+ children: [jsx("div", {
1769
+ className: "flex items-start justify-between gap-3",
1770
+ children: jsxs("div", {
1771
+ className: "flex-1 min-w-0",
1772
+ children: [slotName && shouldShowSlot && jsx(Slot, {
1773
+ name: slotName,
1774
+ fillProps: {
1775
+ active: true,
1776
+ message,
1777
+ index: 0,
1778
+ onMessageClick
1779
+ }
1780
+ }), !hasStreamingSlot && message.message && jsx("div", {
1781
+ className: `min-w-0 relative max-w-full w-full ${isSystemMessage ? 'text-gray-800' : 'text-gray-900'}`,
1782
+ dir: detectTextDirection(message?.message || ''),
1783
+ lang: detectLanguageTag(message?.message || ''),
1784
+ children: (() => {
1785
+ const contentType = detectContentType(message.message);
1786
+ // Markdown and text both go through FormattedMessageContent
1787
+ // which will intelligently detect and render markdown
1788
+ if (contentType === 'code') {
1789
+ return jsx(CodeFormatter, {
1790
+ content: message.message,
1791
+ showCopyDownload: showCopyDownloadOnCodeBlock
1792
+ });
1793
+ } else if (contentType === 'html') {
1794
+ return jsx(AssistantResponseWrapper, {
1795
+ children: jsx("div", {
1796
+ className: getAssistantResponseClasses('min-w-0 relative max-w-full w-full'),
1797
+ dangerouslySetInnerHTML: {
1798
+ __html: prettifyHtmlContent(sanitizeHtml(message.message))
1799
+ }
1800
+ })
1801
+ });
1802
+ } else {
1803
+ // Handle both 'markdown' and 'text' - FormattedMessageContent
1804
+ // will detect markdown patterns and render accordingly
1805
+ return jsx(FormattedMessageContent, {
1806
+ content: message.message,
1807
+ showCopyDownloadOnCodeBlock: showCopyDownloadOnCodeBlock
1808
+ });
1809
+ }
1810
+ })()
1811
+ }), !hasStreamingSlot && tryExtractBuilderBlocks(message?.message)?.length ? jsx("div", {
1812
+ className: "mt-3 p-3 bg-gray-50 rounded-lg border border-gray-200",
1813
+ children: jsx(BuilderLikeRenderer, {
1814
+ blocks: tryExtractBuilderBlocks(message?.message)
1815
+ })
1816
+ }) : null, !hasStreamingSlot && message?.props?.blocks?.length ? jsx("div", {
1817
+ className: "mt-3 p-3 bg-gray-50 rounded-lg border border-gray-200",
1818
+ children: jsx(BuilderLikeRenderer, {
1819
+ blocks: message.props.blocks
1820
+ })
1821
+ }) : null, !hasStreamingSlot && message.files?.totalCount > 0 ? jsx("div", {
1822
+ className: "mt-3 p-3 bg-gray-50 rounded-lg border border-gray-200",
1823
+ children: jsx(FilesList, {
1824
+ uploaded: true,
1825
+ files: message.files.data
1826
+ })
1827
+ }) : null]
1828
+ })
1829
+ }), showTimestamp ? jsx("div", {
1830
+ className: "text-[10px] text-gray-500 mt-1 text-right",
1831
+ children: formatTime(message.createdAt)
1832
+ }) : null]
1833
+ });
1834
+ };
1835
+ const ModernMessageGroupComponent = ({
1836
+ messages,
1837
+ currentUser,
1838
+ showAuthorName = false,
1839
+ showAvatar = false,
1840
+ showTimestamp = false,
1841
+ onOpen,
1842
+ onMessageClick,
1843
+ isDesktopView = false,
1844
+ isSmallScreen = false,
1845
+ sandboxErrors = [],
1846
+ currentFiles = {},
1847
+ onFixError,
1848
+ showCopyDownloadOnCodeBlock = false
1849
+ }) => {
1850
+ // Inject CSS styles for HTML content
1851
+ useInjectStyles();
1852
+ // Filter out non-message items (like date strings)
1853
+ const actualMessages = messages.filter(msg => typeof msg !== 'string');
1854
+ // Group messages by user and time
1855
+ const messageGroups = groupMessagesByUserAndTime(actualMessages);
1856
+ return jsxs("div", {
1857
+ className: `min-h-fit ${isDesktopView ? 'space-y-6' : isSmallScreen ? 'space-y-4' : 'space-y-5'}`,
1858
+ children: [messageGroups.map((group, groupIndex) => {
1859
+ const author = group[0]?.author;
1860
+ return jsx(ModernMessageGroup, {
1861
+ author: author,
1862
+ messages: group,
1863
+ currentUser: currentUser,
1864
+ onOpen: onOpen,
1865
+ onMessageClick: onMessageClick,
1866
+ isDesktopView: isDesktopView,
1867
+ isSmallScreen: isSmallScreen,
1868
+ showAuthorName: showAuthorName,
1869
+ showAvatar: showAvatar,
1870
+ showTimestamp: showTimestamp,
1871
+ showCopyDownloadOnCodeBlock: showCopyDownloadOnCodeBlock
1872
+ }, `group-${groupIndex}-${group[0]?.id}`);
1873
+ }), sandboxErrors?.map(error => jsx("div", {
1874
+ className: "px-2",
1875
+ children: jsx(ErrorFixCard, {
1876
+ error: error,
1877
+ onFixError: onFixError || (() => Promise.resolve()),
1878
+ currentFiles: currentFiles,
1879
+ isFixing: false
1880
+ })
1881
+ }, error.id))]
1882
+ });
1883
+ };export{AssistantResponseWrapper,CodeFormatter,FormattedMessageContent,ModernMessageGroupComponent,detectContentType,groupMessagesByUserAndTime};//# sourceMappingURL=ModernMessageGroup.js.map