@admin-layout/tailwind-chat-pro 12.2.4-alpha.102

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