@messenger-box/tailwind-ui-inbox 10.0.3-alpha.73 → 10.0.3-alpha.74

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 (145) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/lib/components/AIAgent/AIAgent.d.ts +7 -0
  3. package/lib/components/AIAgent/AIAgent.d.ts.map +1 -1
  4. package/lib/components/AIAgent/AIAgent.js +362 -615
  5. package/lib/components/AIAgent/AIAgent.js.map +1 -1
  6. package/lib/components/InboxMessage/InputComponent.d.ts.map +1 -1
  7. package/lib/components/InboxMessage/InputComponent.js +143 -140
  8. package/lib/components/InboxMessage/InputComponent.js.map +1 -1
  9. package/lib/components/InboxMessage/RightSidebarAi.d.ts +23 -0
  10. package/lib/components/InboxMessage/RightSidebarAi.d.ts.map +1 -0
  11. package/lib/components/InboxMessage/RightSidebarAi.js +9 -0
  12. package/lib/components/InboxMessage/RightSidebarAi.js.map +1 -0
  13. package/lib/components/InboxMessage/index.d.ts +1 -0
  14. package/lib/components/InboxMessage/index.d.ts.map +1 -1
  15. package/lib/components/InboxMessage/message-widgets/ErrorFixCard.d.ts +11 -0
  16. package/lib/components/InboxMessage/message-widgets/ErrorFixCard.d.ts.map +1 -0
  17. package/lib/components/InboxMessage/message-widgets/ErrorFixCard.js +194 -0
  18. package/lib/components/InboxMessage/message-widgets/ErrorFixCard.js.map +1 -0
  19. package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.d.ts +5 -1
  20. package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.d.ts.map +1 -1
  21. package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.js +308 -857
  22. package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.js.map +1 -1
  23. package/lib/components/ModelConfigPanel.d.ts +12 -0
  24. package/lib/components/ModelConfigPanel.d.ts.map +1 -0
  25. package/lib/components/ModelConfigPanel.js +304 -0
  26. package/lib/components/ModelConfigPanel.js.map +1 -0
  27. package/lib/components/filler-components/RightSiderBar.d.ts +24 -0
  28. package/lib/components/filler-components/RightSiderBar.d.ts.map +1 -0
  29. package/lib/components/filler-components/RightSiderBar.js +335 -0
  30. package/lib/components/filler-components/RightSiderBar.js.map +1 -0
  31. package/lib/components/index.d.ts +4 -2
  32. package/lib/components/index.d.ts.map +1 -1
  33. package/lib/components/live-code-editor/hybrid-live-editor.d.ts +20 -0
  34. package/lib/components/live-code-editor/hybrid-live-editor.d.ts.map +1 -0
  35. package/lib/components/live-code-editor/hybrid-live-editor.js +68 -0
  36. package/lib/components/live-code-editor/hybrid-live-editor.js.map +1 -0
  37. package/lib/components/live-code-editor/index.d.ts +4 -0
  38. package/lib/components/live-code-editor/index.d.ts.map +1 -0
  39. package/lib/components/live-code-editor/live-code-editor.d.ts +14 -0
  40. package/lib/components/live-code-editor/live-code-editor.d.ts.map +1 -0
  41. package/lib/components/live-code-editor/live-code-editor.js +207 -0
  42. package/lib/components/live-code-editor/live-code-editor.js.map +1 -0
  43. package/lib/components/slot-fill/chat-message-filler.js +1 -1
  44. package/lib/components/slot-fill/chat-message-filler.js.map +1 -1
  45. package/lib/components/slot-fill/index.d.ts +1 -0
  46. package/lib/components/slot-fill/index.d.ts.map +1 -1
  47. package/lib/components/slot-fill/right-sidebar-filler.d.ts +4 -0
  48. package/lib/components/slot-fill/right-sidebar-filler.d.ts.map +1 -0
  49. package/lib/components/slot-fill/right-sidebar-filler.js +13 -0
  50. package/lib/components/slot-fill/right-sidebar-filler.js.map +1 -0
  51. package/lib/components/ui/button.d.ts +9 -0
  52. package/lib/components/ui/button.d.ts.map +1 -0
  53. package/lib/compute.js +1 -2
  54. package/lib/container/AiInbox.d.ts.map +1 -1
  55. package/lib/container/AiLandingInput.d.ts.map +1 -1
  56. package/lib/container/AiLandingInput.js +46 -119
  57. package/lib/container/AiLandingInput.js.map +1 -1
  58. package/lib/container/Inbox.js +1 -1
  59. package/lib/container/Inbox.js.map +1 -1
  60. package/lib/container/InboxAiMessagesLoader.d.ts +0 -21
  61. package/lib/container/InboxAiMessagesLoader.d.ts.map +1 -1
  62. package/lib/container/InboxAiMessagesLoader.js +18 -32
  63. package/lib/container/InboxAiMessagesLoader.js.map +1 -1
  64. package/lib/container/ServiceInbox.js +1 -1
  65. package/lib/container/ServiceInbox.js.map +1 -1
  66. package/lib/container/ThreadMessages.js +1 -1
  67. package/lib/container/ThreadMessages.js.map +1 -1
  68. package/lib/container/ThreadMessagesInbox.js +1 -1
  69. package/lib/container/ThreadMessagesInbox.js.map +1 -1
  70. package/lib/container/Threads.js +1 -1
  71. package/lib/container/Threads.js.map +1 -1
  72. package/lib/container/index.d.ts +2 -1
  73. package/lib/container/index.d.ts.map +1 -1
  74. package/lib/enums/messenger-slot-fill-name-enum.d.ts +2 -1
  75. package/lib/enums/messenger-slot-fill-name-enum.d.ts.map +1 -1
  76. package/lib/enums/messenger-slot-fill-name-enum.js +1 -0
  77. package/lib/enums/messenger-slot-fill-name-enum.js.map +1 -1
  78. package/lib/hooks/index.d.ts +3 -0
  79. package/lib/hooks/index.d.ts.map +1 -0
  80. package/lib/hooks/use-file-sync.d.ts +16 -0
  81. package/lib/hooks/use-file-sync.d.ts.map +1 -0
  82. package/lib/hooks/use-file-sync.js +63 -0
  83. package/lib/hooks/use-file-sync.js.map +1 -0
  84. package/lib/hooks/usePersistentModelConfig.d.ts +15 -0
  85. package/lib/hooks/usePersistentModelConfig.d.ts.map +1 -0
  86. package/lib/hooks/usePersistentModelConfig.js +46 -0
  87. package/lib/hooks/usePersistentModelConfig.js.map +1 -0
  88. package/lib/index.d.ts +4 -2
  89. package/lib/index.d.ts.map +1 -1
  90. package/lib/index.js +1 -1
  91. package/lib/machines/aiAgentMachine.d.ts.map +1 -1
  92. package/lib/machines/aiAgentMachine.js +64 -21
  93. package/lib/machines/aiAgentMachine.js.map +1 -1
  94. package/lib/machines/aiAgentMachine.simple.d.ts +3 -0
  95. package/lib/machines/aiAgentMachine.simple.d.ts.map +1 -0
  96. package/lib/machines/aiAgentMachine.simple.js +108 -0
  97. package/lib/machines/aiAgentMachine.simple.js.map +1 -0
  98. package/lib/machines/index.d.ts +3 -0
  99. package/lib/machines/index.d.ts.map +1 -0
  100. package/lib/module.d.ts +2 -1
  101. package/lib/module.d.ts.map +1 -1
  102. package/lib/module.js +11 -3
  103. package/lib/module.js.map +1 -1
  104. package/lib/routes.json +1 -2
  105. package/lib/templates/InboxWithAi.d.ts.map +1 -1
  106. package/lib/templates/InboxWithAi.js +129 -70
  107. package/lib/templates/InboxWithAi.js.map +1 -1
  108. package/lib/templates/InboxWithAi.tsx +151 -90
  109. package/lib/utils/utils.d.ts +2 -0
  110. package/lib/utils/utils.d.ts.map +1 -0
  111. package/lib/utils/utils.js +3 -0
  112. package/lib/utils/utils.js.map +1 -0
  113. package/package.json +8 -5
  114. package/src/components/AIAgent/AIAgent.tsx +469 -731
  115. package/src/components/AIAgent/AIAgent.tsx.bk +1365 -0
  116. package/src/components/InboxMessage/InputComponent.tsx +2 -1
  117. package/src/components/InboxMessage/RightSidebarAi.tsx +37 -0
  118. package/src/components/InboxMessage/index.ts +1 -0
  119. package/src/components/InboxMessage/message-widgets/ErrorFixCard.tsx +240 -0
  120. package/src/components/InboxMessage/message-widgets/ModernMessageGroup.tsx +337 -1116
  121. package/src/components/ModelConfigPanel.tsx +334 -0
  122. package/src/components/filler-components/RightSiderBar.tsx +408 -0
  123. package/src/components/index.ts +4 -1
  124. package/src/components/live-code-editor/hybrid-live-editor.tsx +105 -0
  125. package/src/components/live-code-editor/index.ts +3 -0
  126. package/src/components/live-code-editor/live-code-editor.tsx +257 -0
  127. package/src/components/slot-fill/index.ts +1 -0
  128. package/src/components/slot-fill/right-sidebar-filler.tsx +39 -0
  129. package/src/components/ui/button.tsx +32 -0
  130. package/src/container/AiInbox.tsx +26 -3
  131. package/src/container/AiLandingInput.tsx +48 -22
  132. package/src/container/InboxAiMessagesLoader.tsx +17 -31
  133. package/src/container/index.ts +2 -0
  134. package/src/enums/messenger-slot-fill-name-enum.ts +1 -0
  135. package/src/hooks/index.ts +2 -0
  136. package/src/hooks/use-file-sync.ts +91 -0
  137. package/src/hooks/usePersistentModelConfig.ts +63 -0
  138. package/src/index.ts +7 -0
  139. package/src/machines/aiAgentMachine.simple.ts +89 -0
  140. package/src/machines/aiAgentMachine.ts +67 -19
  141. package/src/machines/aiAgentMachine.ts.bk +1296 -0
  142. package/src/machines/index.ts +2 -0
  143. package/src/module.tsx +10 -1
  144. package/src/templates/InboxWithAi.tsx +151 -90
  145. package/src/utils/utils.ts +3 -0
@@ -1,7 +1,10 @@
1
1
  import React, { useEffect } from 'react';
2
- import { format, formatDistanceToNow, differenceInMinutes } from 'date-fns';
3
- import { IAuthUser, IPost } from 'common';
2
+ import { format, differenceInMinutes } from 'date-fns';
3
+ import { IAuthUser, IPost, ISandboxError } from 'common';
4
4
  import { FilesList } from '../../inbox';
5
+ import { ErrorFixCard } from './ErrorFixCard';
6
+ import ReactMarkdown from 'react-markdown';
7
+ import remarkGfm from 'remark-gfm';
5
8
 
6
9
  // Enhanced CSS styles for HTML content rendering with prettification
7
10
  const htmlContentStyles = `
@@ -177,216 +180,183 @@ const htmlContentStyles = `
177
180
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
178
181
  }
179
182
 
180
- .html-content .highlight {
181
- background-color: #fef3c7;
182
- padding: 0.25rem 0.5rem;
183
- border-radius: 0.25rem;
184
- border: 1px solid #f59e0b;
185
- }
186
-
187
- .html-content .info-box {
188
- background-color: #dbeafe;
189
- border: 1px solid #3b82f6;
190
- border-radius: 0.5rem;
191
- padding: 1rem;
192
- margin: 1rem 0;
193
- }
194
-
195
- .html-content .warning-box {
196
- background-color: #fef3c7;
197
- border: 1px solid #f59e0b;
198
- border-radius: 0.5rem;
199
- padding: 1rem;
200
- margin: 1rem 0;
183
+ /* Enhanced message container styling */
184
+ .message-container {
185
+ background: #ffffff;
186
+ padding: 16px;
187
+ margin: 12px 0;
188
+ transition: all 0.2s ease-in-out;
201
189
  }
202
190
 
203
- .html-content .success-box {
204
- background-color: #d1fae5;
205
- border: 1px solid #10b981;
206
- border-radius: 0.5rem;
207
- padding: 1rem;
208
- margin: 1rem 0;
209
- }
210
-
211
- .html-content .error-box {
212
- background-color: #fee2e2;
213
- border: 1px solid #ef4444;
214
- border-radius: 0.5rem;
215
- padding: 1rem;
216
- margin: 1rem 0;
191
+ /* Markdown content styling */
192
+ .markdown-content {
193
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
194
+ line-height: 1.6;
195
+ color: #374151;
217
196
  }
218
197
 
219
- .html-content .html-heading {
220
- margin-top: 2rem;
221
- margin-bottom: 1rem;
198
+ .markdown-content h1 {
199
+ font-size: 1.5rem;
222
200
  font-weight: 700;
223
- line-height: 1.2;
201
+ margin-top: 1.5rem;
202
+ margin-bottom: 1rem;
224
203
  color: #111827;
225
- }
226
-
227
- .html-content .html-heading-1 {
228
- font-size: 2rem;
229
- border-bottom: 3px solid #3b82f6;
230
- padding-bottom: 0.75rem;
231
- }
232
-
233
- .html-content .html-heading-2 {
234
- font-size: 1.5rem;
235
- border-bottom: 2px solid #e5e7eb;
236
204
  padding-bottom: 0.5rem;
205
+ border-bottom: 2px solid #e5e7eb;
237
206
  }
238
207
 
239
- .html-content .html-heading-3 {
208
+ .markdown-content h2 {
240
209
  font-size: 1.25rem;
241
- color: #374151;
242
- }
243
-
244
- .html-content .html-paragraph {
245
- margin-bottom: 1.25rem;
246
- line-height: 1.8;
247
- color: #4b5563;
248
- }
249
-
250
- .html-content .html-list {
251
- margin: 1.5rem 0;
252
- padding-left: 2rem;
253
- }
254
-
255
- .html-content .html-list li {
210
+ font-weight: 600;
211
+ margin-top: 1.25rem;
256
212
  margin-bottom: 0.75rem;
257
- line-height: 1.7;
258
- position: relative;
213
+ color: #1f2937;
214
+ padding-bottom: 0.375rem;
215
+ border-bottom: 1px solid #e5e7eb;
259
216
  }
260
217
 
261
- .html-content .html-list-unordered li::before {
262
- content: "•";
263
- color: #3b82f6;
264
- font-weight: bold;
265
- position: absolute;
266
- left: -1.5rem;
218
+ .markdown-content h3 {
219
+ font-size: 1.125rem;
220
+ font-weight: 600;
221
+ margin-top: 1rem;
222
+ margin-bottom: 0.5rem;
223
+ color: #374151;
267
224
  }
268
225
 
269
- .html-content .html-list-ordered {
270
- counter-reset: list-counter;
226
+ .markdown-content p {
227
+ margin-bottom: 0.75rem;
228
+ line-height: 1.6;
229
+ color: #4b5563;
271
230
  }
272
231
 
273
- .html-content .html-list-ordered li {
274
- counter-increment: list-counter;
232
+ .markdown-content ul, .markdown-content ol {
233
+ margin: 1rem 0;
234
+ padding-left: 1.5rem;
275
235
  }
276
236
 
277
- .html-content .html-list-ordered li::before {
278
- content: counter(list-counter) ".";
279
- color: #3b82f6;
280
- font-weight: 600;
281
- position: absolute;
282
- left: -2rem;
237
+ .markdown-content li {
238
+ margin-bottom: 0.5rem;
239
+ line-height: 1.6;
283
240
  }
284
241
 
285
- .html-content .html-inline-code {
242
+ .markdown-content code {
286
243
  background-color: #f3f4f6;
287
- padding: 0.25rem 0.5rem;
288
- border-radius: 0.375rem;
244
+ padding: 0.125rem 0.375rem;
245
+ border-radius: 0.25rem;
289
246
  font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
290
247
  font-size: 0.875rem;
291
248
  color: #dc2626;
292
249
  border: 1px solid #e5e7eb;
293
250
  }
294
251
 
295
- .html-content .html-code-block {
296
- background-color: #1f2937;
297
- color: #f9fafb;
298
- padding: 1.5rem;
299
- border-radius: 0.75rem;
252
+ .markdown-content pre {
253
+ color: #1f2937;
254
+ border-radius: 8px;
300
255
  overflow-x: auto;
301
256
  font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
302
257
  font-size: 0.875rem;
303
- line-height: 1.6;
304
- border: 1px solid #374151;
305
- margin: 2rem 0;
306
- box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
258
+ line-height: 1.5;
259
+ margin: 1rem 0;
260
+ }
261
+
262
+ .markdown-content pre code {
263
+ background-color: transparent;
264
+ padding: 0;
265
+ border: none;
266
+ color: #1f2937;
267
+ font-size: inherit;
307
268
  }
308
269
 
309
- .html-content .html-blockquote {
270
+ .markdown-content blockquote {
310
271
  border-left: 4px solid #3b82f6;
311
- background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
312
- padding: 1.5rem;
313
- margin: 2rem 0;
314
- border-radius: 0.5rem;
272
+ background-color: #f8fafc;
273
+ padding: 1rem;
274
+ margin: 1rem 0;
275
+ border-radius: 6px;
315
276
  font-style: italic;
316
277
  color: #475569;
317
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
278
+ border-top: 1px solid #e5e7eb;
279
+ border-right: 1px solid #e5e7eb;
280
+ border-bottom: 1px solid #e5e7eb;
318
281
  }
319
282
 
320
- .html-content .html-table {
283
+ .markdown-content table {
321
284
  border-collapse: collapse;
322
- border: 2px solid #e5e7eb;
285
+ border: 1px solid #e5e7eb;
323
286
  width: 100%;
324
- margin: 2rem 0;
325
- border-radius: 0.75rem;
287
+ margin: 1rem 0;
288
+ border-radius: 8px;
326
289
  overflow: hidden;
327
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
290
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
328
291
  }
329
292
 
330
- .html-content .html-table-header {
331
- background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
332
- color: white;
333
- font-weight: 700;
334
- padding: 1rem;
293
+ .markdown-content th {
294
+ background-color: #f9fafb;
295
+ color: #374151;
296
+ font-weight: 600;
297
+ padding: 0.75rem;
335
298
  text-align: left;
336
- border-bottom: 2px solid #1d4ed8;
299
+ border-bottom: 1px solid #e5e7eb;
300
+ font-size: 0.875rem;
337
301
  }
338
302
 
339
- .html-content .html-table-cell {
303
+ .markdown-content td {
340
304
  border: 1px solid #e5e7eb;
341
- padding: 1rem;
305
+ padding: 0.75rem;
342
306
  text-align: left;
343
307
  vertical-align: top;
344
308
  background-color: white;
309
+ font-size: 0.875rem;
345
310
  }
346
311
 
347
- .html-content .html-table tr:nth-child(even) .html-table-cell {
312
+ .markdown-content tr:nth-child(even) td {
348
313
  background-color: #f9fafb;
349
314
  }
350
315
 
351
- .html-content .html-table tr:hover .html-table-cell {
316
+ .markdown-content tr:hover td {
352
317
  background-color: #f3f4f6;
353
318
  }
354
319
 
355
- /* Enhanced code block styling */
356
- .html-content pre {
357
- background-color: #1f2937;
358
- color: #f9fafb;
359
- padding: 1.5rem;
360
- border-radius: 0.75rem;
361
- overflow-x: auto;
362
- font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
363
- font-size: 0.875rem;
364
- line-height: 1.6;
365
- border: 1px solid #374151;
366
- margin: 1.5rem 0;
367
- box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
320
+ /* Enhanced list styling */
321
+ .markdown-content ul li {
322
+ position: relative;
323
+ padding-left: 0.5rem;
368
324
  }
369
325
 
370
- .html-content pre code {
371
- background-color: transparent;
372
- padding: 0;
373
- border: none;
374
- color: #f9fafb;
375
- font-size: inherit;
326
+ .markdown-content ul li::before {
327
+ content: "•";
328
+ color: #000;
329
+ font-weight: bold;
330
+ position: absolute;
331
+ left: -1rem;
332
+ }
333
+
334
+ .markdown-content ol li {
335
+ position: relative;
336
+ padding-left: 0.5rem;
337
+ }
338
+
339
+ .markdown-content ol li::before {
340
+ content: counter(list-item) ".";
341
+ font-weight: 600;
342
+ position: absolute;
343
+ left: -1.5rem;
376
344
  }
377
345
 
378
346
  /* Code block container styling */
379
- .html-content .code-block-container {
380
- border-radius: 0.75rem;
347
+ .code-block-container {
348
+ background: #ffffff;
349
+ border: 1px solid #e5e7eb;
350
+ border-radius: 8px;
381
351
  overflow: hidden;
382
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
383
- margin: 1.5rem 0;
352
+ margin: 1rem 0;
353
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
384
354
  }
385
355
 
386
- .html-content .code-block-header {
387
- background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%);
388
- padding: 0.75rem 1rem;
389
- border-bottom: 1px solid #d1d5db;
356
+ .code-block-header {
357
+ background: #f9fafb;
358
+ padding: 0.5rem 1rem;
359
+ border-bottom: 1px solid #e5e7eb;
390
360
  font-weight: 600;
391
361
  color: #374151;
392
362
  font-size: 0.875rem;
@@ -394,48 +364,15 @@ const htmlContentStyles = `
394
364
  letter-spacing: 0.05em;
395
365
  }
396
366
 
397
- .html-content .code-block-content {
398
- background-color: #1f2937;
399
- padding: 1.5rem;
367
+ .code-block-content {
368
+ padding: 1rem;
400
369
  overflow-x: auto;
401
370
  }
402
- `;
403
371
 
404
- // Function to prettify HTML content
405
- const prettifyHtmlContent = (htmlContent: string): string => {
406
- // Add semantic classes for better styling
407
- let prettified = htmlContent;
408
-
409
- // Enhance headings with better styling
410
- prettified = prettified.replace(/<h([1-6])>/g, '<h$1 class="html-heading html-heading-$1">');
411
-
412
- // Enhance lists with better styling
413
- prettified = prettified.replace(/<ul>/g, '<ul class="html-list html-list-unordered">');
414
- prettified = prettified.replace(/<ol>/g, '<ol class="html-list html-list-ordered">');
415
-
416
- // Enhance paragraphs with better spacing
417
- prettified = prettified.replace(/<p>/g, '<p class="html-paragraph">');
418
-
419
- // Enhance code blocks
420
- prettified = prettified.replace(/<code>/g, '<code class="html-inline-code">');
421
- prettified = prettified.replace(/<pre>/g, '<pre class="html-code-block">');
422
-
423
- // Enhance blockquotes
424
- prettified = prettified.replace(/<blockquote>/g, '<blockquote class="html-blockquote">');
425
-
426
- // Enhance tables
427
- prettified = prettified.replace(/<table>/g, '<table class="html-table">');
428
- prettified = prettified.replace(/<th>/g, '<th class="html-table-header">');
429
- prettified = prettified.replace(/<td>/g, '<td class="html-table-cell">');
430
-
431
- // Add info boxes for certain content patterns
432
- prettified = prettified.replace(
433
- /<p>(Note|Tip|Info|Warning|Error):\s*(.*?)<\/p>/gi,
434
- '<div class="html-info-box"><strong>$1:</strong> $2</div>',
435
- );
436
-
437
- return prettified;
438
- };
372
+ .hover\:bg-gray-100:hover {
373
+ background-color: transparent !important;
374
+ }
375
+ `;
439
376
 
440
377
  // Hook to inject CSS styles
441
378
  const useInjectStyles = () => {
@@ -468,6 +405,10 @@ interface ModernMessageGroupProps {
468
405
  onMessageClick: (msg: IPost) => void;
469
406
  isDesktopView?: boolean;
470
407
  isSmallScreen?: boolean;
408
+ sandboxErrors?: ISandboxError[];
409
+ currentFiles?: Record<string, string>;
410
+ onFixError?: (errorMessage: string) => Promise<void>;
411
+ onRecreateSandbox?: (fragmentId: string) => void;
471
412
  }
472
413
 
473
414
  interface MessageGroupProps {
@@ -539,56 +480,6 @@ const isProbablyHTML = (value: string): boolean => {
539
480
  );
540
481
  };
541
482
 
542
- // Detect if a string looks like a code block
543
- const isCodeBlock = (text: string): boolean => {
544
- if (!text || text.length < 10) return false;
545
-
546
- // Check for common programming patterns
547
- const codePatterns = [
548
- /\b(if|else|for|while|switch|case|break|continue|return|function|class|import|export|const|let|var|default)\b/,
549
- /\b(public|private|protected|static|final|abstract|interface|extends|implements)\b/,
550
- /\b(try|catch|finally|throw|throws)\b/,
551
- /\b(new|this|super|null|undefined|true|false)\b/,
552
- // Narrow punctuation: exclude parentheses, brackets, hyphen, question/colon to reduce false positives
553
- /[{}<>;=+*/%&|^~]/,
554
- /\b(System\.out\.println|console\.log|print|printf)\b/,
555
- /\b(int|string|boolean|float|double|long|char|byte|short)\b/,
556
- /\b(void|int|main|args)\b/,
557
- /\/\/.*$/m, // Single line comments
558
- /\/\*[\s\S]*?\*\//m, // Multi-line comments
559
- /^\s*[a-zA-Z_$][a-zA-Z0-9_$]*\s*[=:]\s*/, // Variable assignment
560
- /^\s*[a-zA-Z_$][a-zA-Z0-9_$]*\s*\(.*\)\s*{/, // Function definition
561
- /\bexport\s+default\b/, // Export default statements
562
- /\breturn\s+\(/, // Return with JSX
563
- /\bJSX\.Element\b/, // JSX types
564
- ];
565
-
566
- // Count how many patterns match
567
- let matchCount = 0;
568
- codePatterns.forEach((pattern) => {
569
- if (pattern.test(text)) {
570
- matchCount++;
571
- }
572
- });
573
-
574
- // Debug logging
575
- console.log('Code block detection for:', text.substring(0, 100) + '...');
576
- console.log('Match count:', matchCount);
577
- console.log('Contains export default:', /\bexport\s+default\b/.test(text));
578
- console.log('Contains function:', /\bfunction\b/.test(text));
579
- console.log('Contains braces:', /[{}<>;]/.test(text));
580
-
581
- // If multiple patterns match or if it contains specific strong indicators, consider it code
582
- const result =
583
- matchCount >= 2 ||
584
- /\b(if|else|for|while|function|class|export)\b/.test(text) ||
585
- /[{}<>;]/.test(text) ||
586
- /\bexport\s+default\b/.test(text);
587
-
588
- console.log('Is code block:', result);
589
- return result;
590
- };
591
-
592
483
  // Enhanced sanitizer that allows most HTML tags while maintaining security
593
484
  const sanitizeHtml = (html: string): string => {
594
485
  try {
@@ -638,6 +529,42 @@ const sanitizeHtml = (html: string): string => {
638
529
  }
639
530
  };
640
531
 
532
+ // Function to prettify HTML content
533
+ const prettifyHtmlContent = (htmlContent: string): string => {
534
+ // Add semantic classes for better styling
535
+ let prettified = htmlContent;
536
+
537
+ // Enhance headings with better styling
538
+ prettified = prettified.replace(/<h([1-6])>/g, '<h$1 class="html-heading html-heading-$1">');
539
+
540
+ // Enhance lists with better styling
541
+ prettified = prettified.replace(/<ul>/g, '<ul class="html-list html-list-unordered">');
542
+ prettified = prettified.replace(/<ol>/g, '<ol class="html-list html-list-ordered">');
543
+
544
+ // Enhance paragraphs with better spacing
545
+ prettified = prettified.replace(/<p>/g, '<p class="html-paragraph">');
546
+
547
+ // Enhance code blocks
548
+ prettified = prettified.replace(/<code>/g, '<code class="html-inline-code">');
549
+ prettified = prettified.replace(/<pre>/g, '<pre class="html-code-block">');
550
+
551
+ // Enhance blockquotes
552
+ prettified = prettified.replace(/<blockquote>/g, '<blockquote class="html-blockquote">');
553
+
554
+ // Enhance tables
555
+ prettified = prettified.replace(/<table>/g, '<table class="html-table">');
556
+ prettified = prettified.replace(/<th>/g, '<th class="html-table-header">');
557
+ prettified = prettified.replace(/<td>/g, '<td class="html-table-cell">');
558
+
559
+ // Add info boxes for certain content patterns
560
+ prettified = prettified.replace(
561
+ /<p>(Note|Tip|Info|Warning|Error):\s*(.*?)<\/p>/gi,
562
+ '<div class="html-info-box"><strong>$1:</strong> $2</div>',
563
+ );
564
+
565
+ return prettified;
566
+ };
567
+
641
568
  // Minimal Builder-like blocks renderer (text/image/button/columns)
642
569
  const BuilderLikeRenderer: React.FC<{ blocks?: any[] }> = ({ blocks }) => {
643
570
  if (!blocks || !Array.isArray(blocks) || blocks.length === 0) return null;
@@ -653,7 +580,7 @@ const BuilderLikeRenderer: React.FC<{ blocks?: any[] }> = ({ blocks }) => {
653
580
  return (
654
581
  <div
655
582
  key={idx}
656
- className="prose prose-sm max-w-none text-gray-800"
583
+ className="max-w-none text-gray-800"
657
584
  dangerouslySetInnerHTML={{ __html: html }}
658
585
  />
659
586
  );
@@ -700,880 +627,147 @@ const BuilderLikeRenderer: React.FC<{ blocks?: any[] }> = ({ blocks }) => {
700
627
  }
701
628
 
702
629
  return (
703
- <pre key={idx} className="text-xs text-gray-600 bg-gray-50 p-2 rounded overflow-x-auto not-prose">
704
- {JSON.stringify(block, null, 2)}
705
- </pre>
630
+ <div key={idx} className="message-container">
631
+ <pre className="text-xs text-gray-600 overflow-x-auto">{JSON.stringify(block, null, 2)}</pre>
632
+ </div>
706
633
  );
707
634
  })}
708
635
  </div>
709
636
  );
710
637
  };
711
638
 
712
- // Enhanced markdown-like renderer for rich text content
713
- // Helper: render multiple inline lists from a single paragraph like
714
- // "Intro A: - item1 - item2 Intro B: - item3 - item4"
715
- const tryRenderMultipleInlineLists = (text: string): React.ReactNode[] | null => {
716
- if (!text || !text.includes(' - ') || !text.includes(':')) return null;
717
- // Split while keeping the section headers (ending with ":")
718
- const segments = text.split(/([^:]+:\s*)/).filter((s) => s.length > 0);
719
- if (segments.length < 3) return null; // need at least header + content
720
-
721
- const nodes: React.ReactNode[] = [];
722
- // Handle leading text before the first header
723
- if (segments[0] && !segments[0].endsWith(':')) {
724
- const leading = segments[0].trim();
725
- if (leading) {
726
- nodes.push(
727
- <p key={`inline-leading`} className="text-gray-700 leading-relaxed">
728
- {leading}
729
- </p>,
730
- );
731
- }
732
- }
733
-
734
- for (let i = 1; i < segments.length; i += 2) {
735
- const header = (segments[i] || '').trim();
736
- const rest = (segments[i + 1] || '').trim();
737
- if (!header.endsWith(':')) continue;
738
- const parts = rest.split(/\s-\s+/).filter((p) => p.trim());
739
- // When split on " - ", the first element is the tail right after header; ignore if empty
740
- const items = parts.length && !rest.startsWith('- ') ? parts.slice(1) : parts;
741
- if (items.length >= 2) {
742
- nodes.push(
743
- <div key={`inline-group-${i}`} className="space-y-2">
744
- <p className="text-gray-900 font-semibold leading-relaxed">{header.replace(/:$/, '')}</p>
745
- <ul className="space-y-2">
746
- {items.map((it, idx) => (
747
- <li key={`inline-item-${i}-${idx}`} className="flex items-start space-x-3 items-center">
748
- <svg
749
- className="w-5 h-5 text-black mt-0.5 flex-shrink-0"
750
- fill="currentColor"
751
- viewBox="0 0 20 20"
752
- >
753
- <path
754
- fillRule="evenodd"
755
- d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
756
- clipRule="evenodd"
757
- />
758
- </svg>
759
- <span className="text-gray-700 leading-relaxed">{it.trim()}</span>
760
- </li>
761
- ))}
762
- </ul>
763
- </div>,
764
- );
765
- } else {
766
- // Not enough items; treat as plain text
767
- const combined = `${header} ${rest}`.trim();
768
- if (combined) {
769
- nodes.push(
770
- <p key={`inline-fallback-${i}`} className="text-gray-700 leading-relaxed">
771
- {combined}
772
- </p>,
773
- );
774
- }
775
- }
776
- }
777
-
778
- return nodes.length ? nodes : null;
779
- };
780
-
639
+ // Enhanced markdown renderer using react-markdown with remark-gfm
781
640
  const FormattedMessageContent: React.FC<{ content: string }> = ({ content }) => {
782
641
  if (!content) return null;
783
642
 
784
- // Debug logging for content
785
- console.log('FormattedMessageContent received:', content.substring(0, 200) + '...');
786
-
787
- // Direct check for a code-looking message (common for AI replies with fenced blocks)
788
- if (
789
- content.includes('export default function Home()') ||
790
- (content.includes('export default') && content.includes('function') && content.includes('return'))
791
- ) {
792
- console.log('Direct code pattern match detected!');
793
-
794
- // If content has fenced code, extract language and code, removing the fences
795
- const fenceMatch = content.match(/```([\w-]*)\n?([\s\S]*?)```/);
796
- let language = 'typescript';
797
- let codeBody = content;
798
- if (fenceMatch) {
799
- language = (fenceMatch[1] || 'code').trim() || 'code';
800
- codeBody = fenceMatch[2];
801
- }
802
-
803
- // Clean up the code content for better display
804
- const cleanCode = codeBody
805
- .replace(/^\s+/, '') // Remove leading whitespace
806
- .replace(/\s+$/, '') // Remove trailing whitespace
807
- .split('\n')
808
- .map((line) => line.trim())
809
- .filter((line) => line.length > 0)
810
- .join('\n');
643
+ // Check if content contains markdown patterns
644
+ const hasMarkdown = /[#*`>|]/.test(content) || content.includes('```');
811
645
 
646
+ if (hasMarkdown) {
812
647
  return (
813
- <div className="rounded-xl border border-gray-200 bg-gray-50 overflow-hidden">
814
- <div className="px-4 py-2 text-sm font-bold text-gray-900">{language}</div>
815
- <div className="px-4 pb-4 overflow-x-auto">
816
- <pre className="text-sm text-gray-900 font-mono leading-relaxed whitespace-pre-wrap not-prose">
817
- <code>{cleanCode}</code>
818
- </pre>
819
- </div>
820
- </div>
821
- );
822
- }
823
-
824
- // Normalize escaped fences (\``` -> ```)
825
- const normalizedContent = content.replace(/\\`{3}/g, '```');
826
-
827
- // Global fenced-code parse as an early pass (handles cases where block splitting interferes)
828
- if (normalizedContent.includes('```')) {
829
- const parts: React.ReactNode[] = [];
830
- const pushTextBlock = (text: string, key: string) => {
831
- const t = (text || '').trim();
832
- if (!t) return;
833
- // Try to render multiple inline lists in one paragraph
834
- const multiInline = tryRenderMultipleInlineLists(t);
835
- if (multiInline) {
836
- multiInline.forEach((n, idx) =>
837
- parts.push(<React.Fragment key={`${key}-mil-${idx}`}>{n}</React.Fragment>),
838
- );
839
- return;
840
- }
841
- if (t.startsWith('# ')) {
842
- parts.push(
843
- <h1
844
- key={key}
845
- className="text-2xl font-bold text-gray-900 mb-4 bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent"
846
- >
847
- {t.substring(2)}
848
- </h1>,
849
- );
850
- return;
851
- }
852
- if (t.startsWith('## ')) {
853
- parts.push(
854
- <h2 key={key} className="text-xl font-semibold text-gray-800 mb-3 border-b border-gray-200 pb-2">
855
- {t.substring(3)}
856
- </h2>,
857
- );
858
- return;
859
- }
860
- if (t.startsWith('### ')) {
861
- parts.push(
862
- <h3 key={key} className="text-lg font-medium text-gray-700 mb-2">
863
- {t.substring(4)}
864
- </h3>,
865
- );
866
- return;
867
- }
868
- // Inline list heuristic: bullet items written on one line separated by " - "
869
- if (!t.includes('\n') && t.includes(' - ') && t.split(' - ').length >= 3) {
870
- const [intro, ...rawItems] = t.split(/\s-\s+/);
871
- parts.push(
872
- <div key={`${key}-inline-list`} className="space-y-2">
873
- {intro.trim() ? <p className="text-gray-700 leading-relaxed">{intro.trim()}</p> : null}
874
- <ul className="space-y-2">
875
- {rawItems.map((item, itemIndex) => (
876
- <li
877
- key={`${key}-inli-${itemIndex}`}
878
- className="flex items-start space-x-3 items-center"
879
- >
880
- <svg
881
- className="w-5 h-5 text-black mt-0.5 flex-shrink-0"
882
- fill="currentColor"
883
- viewBox="0 0 20 20"
648
+ <div className="message-container">
649
+ <div className="markdown-content">
650
+ <ReactMarkdown
651
+ remarkPlugins={[remarkGfm]}
652
+ components={{
653
+ // Custom styling for code blocks
654
+ code: ({ className, children, ...props }: any) => {
655
+ const match = /language-(\w+)/.exec(className || '');
656
+ const isInline = !className?.includes('language-');
657
+ return !isInline ? (
658
+ <div className="code-block-container">
659
+ <div className="code-block-header">{match?.[1] || 'code'}</div>
660
+ <div className="code-block-content">
661
+ <pre className="text-sm text-gray-900 font-mono leading-relaxed whitespace-pre-wrap">
662
+ <code className={className} {...props}>
663
+ {children}
664
+ </code>
665
+ </pre>
666
+ </div>
667
+ </div>
668
+ ) : (
669
+ <code
670
+ className="bg-gray-100 px-1.5 py-0.5 rounded text-sm font-mono text-gray-800 border border-gray-200"
671
+ {...props}
884
672
  >
885
- <path
886
- fillRule="evenodd"
887
- d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
888
- clipRule="evenodd"
889
- />
890
- </svg>
891
- <span className="text-gray-700 leading-relaxed">{item.trim()}</span>
673
+ {children}
674
+ </code>
675
+ );
676
+ },
677
+ // Custom styling for headings
678
+ h1: ({ children, ...props }) => (
679
+ <h1 className="text-xl font-bold text-gray-900 mb-3" {...props}>
680
+ {children}
681
+ </h1>
682
+ ),
683
+ h2: ({ children, ...props }) => (
684
+ <h2 className="text-lg font-semibold text-gray-800 mb-2" {...props}>
685
+ {children}
686
+ </h2>
687
+ ),
688
+ h3: ({ children, ...props }) => (
689
+ <h3 className="text-base font-medium text-gray-700 mb-2" {...props}>
690
+ {children}
691
+ </h3>
692
+ ),
693
+ // Custom styling for lists
694
+ ul: ({ children, ...props }) => (
695
+ <ul className="space-y-1" {...props}>
696
+ {children}
697
+ </ul>
698
+ ),
699
+ ol: ({ children, ...props }) => (
700
+ <ol className="space-y-1" {...props}>
701
+ {children}
702
+ </ol>
703
+ ),
704
+ li: ({ children, ...props }) => (
705
+ <li className="text-gray-700 leading-relaxed" {...props}>
706
+ {children}
892
707
  </li>
893
- ))}
894
- </ul>
895
- </div>,
896
- );
897
- return;
898
- }
899
- if (t.startsWith('- ') || t.startsWith('• ')) {
900
- const items = t.split('\n').filter((line) => line.trim());
901
- parts.push(
902
- <div key={key} className="bg-blue-50 border-l-4 border-blue-500 p-4 rounded-r-lg">
903
- <ul className="space-y-2">
904
- {items.map((item, itemIndex) => (
905
- <li key={`${key}-li-${itemIndex}`} className="flex items-start space-x-3 items-center">
906
- <svg
907
- className="w-5 h-5 text-black mt-0.5 flex-shrink-0"
908
- fill="currentColor"
909
- viewBox="0 0 20 20"
708
+ ),
709
+ // Custom styling for blockquotes
710
+ blockquote: ({ children, ...props }) => (
711
+ <blockquote
712
+ className="border-l-4 border-blue-400 pl-3 py-2 bg-blue-50 rounded-r-md my-3"
713
+ {...props}
714
+ >
715
+ {children}
716
+ </blockquote>
717
+ ),
718
+ // Custom styling for tables
719
+ table: ({ children, ...props }) => (
720
+ <div className="overflow-x-auto my-3">
721
+ <table
722
+ className="min-w-full border border-gray-200 rounded-md overflow-hidden"
723
+ {...props}
910
724
  >
911
- <path
912
- fillRule="evenodd"
913
- d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
914
- clipRule="evenodd"
915
- />
916
- </svg>
917
- <span className="text-gray-700 leading-relaxed">
918
- {item.replace(/^[\u2212\-•]\s*/, '')}
919
- </span>
920
- </li>
921
- ))}
922
- </ul>
923
- </div>,
924
- );
925
- return;
926
- }
927
- if (isProbablyHTML(t)) {
928
- parts.push(
929
- <div
930
- key={key}
931
- className="prose prose-sm max-w-none text-gray-800"
932
- dangerouslySetInnerHTML={{ __html: sanitizeHtml(t) }}
933
- />,
934
- );
935
- return;
936
- }
937
- if (t.includes('**') || t.includes('`')) {
938
- const formatted = t
939
- .replace(
940
- /\*\*(.*?)\*\*/g,
941
- '<span class="font-semibold text-gray-900 bg-yellow-100 px-1 py-0.5 rounded">$1</span>',
942
- )
943
- .replace(
944
- /`([^`]+)`/g,
945
- '<code class="bg-gray-100 px-2 py-1 rounded text-sm font-mono text-gray-800">$1</code>',
946
- );
947
- parts.push(
948
- <div
949
- key={key}
950
- className="text-gray-700 leading-relaxed"
951
- dangerouslySetInnerHTML={{ __html: sanitizeHtml(formatted) }}
952
- />,
953
- );
954
- return;
955
- }
956
-
957
- // Check if the text looks like a code block (contains programming keywords, syntax, etc.)
958
- if (isCodeBlock(t)) {
959
- // Clean up the code content for better display
960
- const cleanCode = t
961
- .replace(/^\s+/, '') // Remove leading whitespace
962
- .replace(/\s+$/, '') // Remove trailing whitespace
963
- .split('\n')
964
- .map((line) => line.trim())
965
- .filter((line) => line.length > 0)
966
- .join('\n');
967
-
968
- parts.push(
969
- <div key={key} className="rounded-xl border border-gray-200 bg-gray-50 overflow-hidden">
970
- <div className="px-4 py-2 text-sm font-bold text-gray-900">typescript</div>
971
- <div className="px-4 pb-4 overflow-x-auto">
972
- <pre className="text-sm text-gray-900 font-mono leading-relaxed whitespace-pre-wrap not-prose">
973
- <code>{cleanCode}</code>
974
- </pre>
975
- </div>
976
- </div>,
977
- );
978
- return;
979
- }
980
- parts.push(
981
- <p key={key} className="text-gray-700 leading-relaxed whitespace-pre-wrap">
982
- {t}
983
- </p>,
984
- );
985
- };
986
-
987
- // Improved regex to handle multiline code blocks better
988
- const codeRegex = /```([\w-]*)\n?([\s\S]*?)```/g;
989
- let lastIndex = 0;
990
- let m: RegExpExecArray | null;
991
-
992
- let foundAnyFence = false;
993
- while ((m = codeRegex.exec(normalizedContent)) !== null) {
994
- foundAnyFence = true;
995
- const [full, lang, code] = m;
996
- const before = normalizedContent.slice(lastIndex, m.index);
997
- pushTextBlock(before, `global-before-${lastIndex}`);
998
-
999
- const language = (lang || '').trim() || 'code';
1000
- // Clean up the code content properly
1001
- const cleanCode = code
1002
- .replace(/^\s+/, '') // Remove leading whitespace
1003
- .replace(/\s+$/, '') // Remove trailing whitespace
1004
- .split('\n')
1005
- .map((line) => line.trim())
1006
- .filter((line) => line.length > 0)
1007
- .join('\n');
1008
-
1009
- parts.push(
1010
- <div
1011
- key={`global-code-${m.index}`}
1012
- className="rounded-xl border border-gray-200 bg-gray-50 overflow-hidden shadow-sm"
1013
- >
1014
- <div className="px-4 py-2 text-sm font-bold text-gray-900 bg-gray-100 border-b border-gray-200">
1015
- {language}
1016
- </div>
1017
- <div className="px-4 py-4 overflow-x-auto bg-white">
1018
- <pre className="text-sm text-gray-900 font-mono leading-relaxed whitespace-pre-wrap not-prose">
1019
- <code>{cleanCode}</code>
1020
- </pre>
1021
- </div>
1022
- </div>,
1023
- );
1024
- lastIndex = m.index + full.length;
1025
- }
1026
- const after = normalizedContent.slice(lastIndex);
1027
-
1028
- if (!foundAnyFence) {
1029
- // Fallback: opening fence exists but no closing fence found
1030
- const fenceIndex = normalizedContent.indexOf('```');
1031
- const before = normalizedContent.slice(0, fenceIndex);
1032
- const afterFence = normalizedContent.slice(fenceIndex + 3); // skip opening backticks
1033
- // Try to read language up to end of line. If none, treat whole remainder as code.
1034
- const firstNewline = afterFence.indexOf('\n');
1035
- const language = firstNewline >= 0 ? afterFence.slice(0, firstNewline).trim() || 'code' : 'code';
1036
- const rawCodeBody = firstNewline >= 0 ? afterFence.slice(firstNewline + 1) : afterFence;
1037
- const codeBody = rawCodeBody
1038
- .replace(/^\s+/, '')
1039
- .replace(/\s+$/, '')
1040
- .split('\n')
1041
- .map((line) => line.trimEnd())
1042
- .join('\n');
1043
-
1044
- pushTextBlock(before, `global-fallback-before-${fenceIndex}`);
1045
- parts.push(
1046
- <div
1047
- key={`global-fallback-code-${fenceIndex}`}
1048
- className="rounded-xl border border-gray-200 bg-gray-50 overflow-hidden shadow-sm"
1049
- >
1050
- <div className="px-4 py-2 text-sm font-bold text-gray-900 bg-gray-100 border-b border-gray-200">
1051
- {language}
1052
- </div>
1053
- <div className="px-4 py-4 overflow-x-auto bg-white">
1054
- <pre className="text-sm text-gray-900 font-mono leading-relaxed whitespace-pre-wrap not-prose">
1055
- <code>{codeBody}</code>
1056
- </pre>
1057
- </div>
1058
- </div>,
1059
- );
1060
- } else {
1061
- // If there's a dangling opening fence in the tail, render it as a fallback code block
1062
- if (after.includes('```')) {
1063
- const fenceIndexTail = after.indexOf('```');
1064
- const beforeTail = after.slice(0, fenceIndexTail);
1065
- const afterFenceTail = after.slice(fenceIndexTail + 3);
1066
- const firstNewlineTail = afterFenceTail.indexOf('\n');
1067
- const languageTail =
1068
- firstNewlineTail >= 0 ? afterFenceTail.slice(0, firstNewlineTail).trim() || 'code' : 'code';
1069
- const rawCodeTail = firstNewlineTail >= 0 ? afterFenceTail.slice(firstNewlineTail + 1) : afterFenceTail;
1070
- const cleanTail = rawCodeTail
1071
- .replace(/^\s+/, '')
1072
- .replace(/\s+$/, '')
1073
- .split('\n')
1074
- .map((line) => line.trimEnd())
1075
- .join('\n');
1076
-
1077
- pushTextBlock(beforeTail, `global-after-text-${lastIndex}`);
1078
- parts.push(
1079
- <div
1080
- key={`global-after-fallback-code-${lastIndex}`}
1081
- className="rounded-xl border border-gray-200 bg-gray-50 overflow-hidden shadow-sm"
725
+ {children}
726
+ </table>
727
+ </div>
728
+ ),
729
+ th: ({ children, ...props }) => (
730
+ <th
731
+ className="px-3 py-2 text-left font-medium text-gray-700 border border-gray-200"
732
+ {...props}
733
+ >
734
+ {children}
735
+ </th>
736
+ ),
737
+ td: ({ children, ...props }) => (
738
+ <td className="px-3 py-2 border border-gray-200 text-gray-700" {...props}>
739
+ {children}
740
+ </td>
741
+ ),
742
+ // Custom styling for task lists (from remark-gfm)
743
+ input: ({ checked, ...props }: any) => (
744
+ <input
745
+ type="checkbox"
746
+ checked={checked}
747
+ readOnly
748
+ className="mr-2 mt-1 h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
749
+ {...props}
750
+ />
751
+ ),
752
+ // Custom styling for strikethrough (from remark-gfm)
753
+ del: ({ children, ...props }) => (
754
+ <del className="text-gray-500 line-through" {...props}>
755
+ {children}
756
+ </del>
757
+ ),
758
+ }}
1082
759
  >
1083
- <div className="px-4 py-2 text-sm font-bold text-gray-900 bg-gray-100 border-b border-gray-200">
1084
- {languageTail}
1085
- </div>
1086
- <div className="px-4 py-4 overflow-x-auto bg-white">
1087
- <pre className="text-sm text-gray-900 font-mono leading-relaxed whitespace-pre-wrap not-prose">
1088
- <code>{cleanTail}</code>
1089
- </pre>
1090
- </div>
1091
- </div>,
1092
- );
1093
- } else {
1094
- pushTextBlock(after, `global-after-${lastIndex}`);
1095
- }
1096
- }
1097
- return <div className="space-y-4">{parts}</div>;
760
+ {content}
761
+ </ReactMarkdown>
762
+ </div>
763
+ </div>
764
+ );
1098
765
  }
1099
766
 
1100
- // Split content into blocks for better formatting
1101
- const blocks = normalizedContent.split('\n\n').filter((block) => block.trim());
1102
-
1103
- // Debug logging for blocks
1104
- console.log(
1105
- 'Content blocks:',
1106
- blocks.map((b) => b.substring(0, 100) + '...'),
1107
- );
1108
-
767
+ // For plain text content
1109
768
  return (
1110
- <div className="space-y-4">
1111
- {blocks.map((block, index) => {
1112
- const trimmedBlock = block.trim();
1113
-
1114
- // Debug logging for individual blocks
1115
- console.log(`Block ${index}:`, trimmedBlock.substring(0, 100) + '...');
1116
- console.log(`Block ${index} isCodeBlock:`, isCodeBlock(trimmedBlock));
1117
-
1118
- // If the block contains fenced code anywhere, render text parts + code parts in order
1119
- if (trimmedBlock.includes('```')) {
1120
- const parts: React.ReactNode[] = [];
1121
- const pushTextBlock = (text: string, key: string) => {
1122
- const t = (text || '').trim();
1123
- if (!t) return;
1124
- const multiInlineInner = tryRenderMultipleInlineLists(t);
1125
- if (multiInlineInner) {
1126
- multiInlineInner.forEach((n, idx) =>
1127
- parts.push(<React.Fragment key={`${key}-mil-${idx}`}>{n}</React.Fragment>),
1128
- );
1129
- return;
1130
- }
1131
- if (t.startsWith('# ')) {
1132
- parts.push(
1133
- <h1
1134
- key={key}
1135
- className="text-2xl font-bold text-gray-900 mb-4 bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent"
1136
- >
1137
- {t.substring(2)}
1138
- </h1>,
1139
- );
1140
- return;
1141
- }
1142
- if (t.startsWith('## ')) {
1143
- parts.push(
1144
- <h2
1145
- key={key}
1146
- className="text-xl font-semibold text-gray-800 mb-3 border-b border-gray-200 pb-2"
1147
- >
1148
- {t.substring(3)}
1149
- </h2>,
1150
- );
1151
- return;
1152
- }
1153
- if (t.startsWith('### ')) {
1154
- parts.push(
1155
- <h3 key={key} className="text-lg font-medium text-gray-700 mb-2">
1156
- {t.substring(4)}
1157
- </h3>,
1158
- );
1159
- return;
1160
- }
1161
- // Inline list heuristic inside block
1162
- if (!t.includes('\n') && t.includes(' - ') && t.split(' - ').length >= 3) {
1163
- const [intro, ...rawItems] = t.split(/\s-\s+/);
1164
- parts.push(
1165
- <div key={`${key}-inline-list`} className="space-y-2">
1166
- {intro.trim() ? (
1167
- <p className="text-gray-700 leading-relaxed">{intro.trim()}</p>
1168
- ) : null}
1169
- <ul className="space-y-2">
1170
- {rawItems.map((item, itemIndex) => (
1171
- <li
1172
- key={`${key}-inli-${itemIndex}`}
1173
- className="flex items-start space-x-3 items-center"
1174
- >
1175
- <svg
1176
- className="w-5 h-5 text-black mt-0.5 flex-shrink-0"
1177
- fill="currentColor"
1178
- viewBox="0 0 20 20"
1179
- >
1180
- <path
1181
- fillRule="evenodd"
1182
- d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
1183
- clipRule="evenodd"
1184
- />
1185
- </svg>
1186
- <span className="text-gray-700 leading-relaxed">{item.trim()}</span>
1187
- </li>
1188
- ))}
1189
- </ul>
1190
- </div>,
1191
- );
1192
- return;
1193
- }
1194
- if (t.startsWith('- ') || t.startsWith('• ')) {
1195
- const items = t.split('\n').filter((line) => line.trim());
1196
- parts.push(
1197
- // <div key={key} className="bg-blue-50 border-l-4 border-blue-500 p-4 rounded-r-lg">
1198
- <div key={key} className=" p-4 rounded-r-lg">
1199
- <ul className="space-y-2">
1200
- {items.map((item, itemIndex) => (
1201
- <li
1202
- key={`${key}-li-${itemIndex}`}
1203
- className="flex items-start space-x-3 items-center"
1204
- >
1205
- <svg
1206
- className="w-5 h-5 text-black mt-0.5 flex-shrink-0"
1207
- fill="currentColor"
1208
- viewBox="0 0 20 20"
1209
- >
1210
- <path
1211
- fillRule="evenodd"
1212
- d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
1213
- clipRule="evenodd"
1214
- />
1215
- </svg>
1216
- <span className="text-gray-700 leading-relaxed">
1217
- {item.replace(/^[\u2212\-•]\s*/, '')}
1218
- </span>
1219
- </li>
1220
- ))}
1221
- </ul>
1222
- </div>,
1223
- );
1224
- return;
1225
- }
1226
- if (t.includes('**') || t.includes('`')) {
1227
- const formatted = t
1228
- .replace(
1229
- /\*\*(.*?)\*\*/g,
1230
- '<span class="font-semibold text-gray-900 bg-yellow-100 px-1 py-0.5 rounded">$1</span>',
1231
- )
1232
- .replace(
1233
- /`([^`]+)`/g,
1234
- '<code class="bg-gray-100 px-2 py-1 rounded text-sm font-mono text-gray-800">$1</code>',
1235
- );
1236
- parts.push(
1237
- <div
1238
- key={key}
1239
- className="text-gray-700 leading-relaxed"
1240
- dangerouslySetInnerHTML={{ __html: sanitizeHtml(formatted) }}
1241
- />,
1242
- );
1243
- return;
1244
- }
1245
-
1246
- // Check if the text looks like a code block (contains programming keywords, syntax, etc.)
1247
- if (isCodeBlock(t)) {
1248
- // Clean up the code content for better display
1249
- const cleanCode = t
1250
- .replace(/^\s+/, '') // Remove leading whitespace
1251
- .replace(/\s+$/, '') // Remove trailing whitespace
1252
- .split('\n')
1253
- .map((line) => line.trim())
1254
- .filter((line) => line.length > 0)
1255
- .join('\n');
1256
-
1257
- parts.push(
1258
- <div key={key} className="rounded-xl border border-gray-200 bg-gray-50 overflow-hidden">
1259
- <div className="px-4 py-2 text-sm font-bold text-gray-900">typescript</div>
1260
- <div className="px-4 pb-4 overflow-x-auto">
1261
- <pre className="text-sm text-gray-900 font-mono leading-relaxed whitespace-pre-wrap not-prose">
1262
- <code>{cleanCode}</code>
1263
- </pre>
1264
- </div>
1265
- </div>,
1266
- );
1267
- return;
1268
- }
1269
-
1270
- parts.push(
1271
- <p key={key} className="text-gray-700 leading-relaxed whitespace-pre-wrap">
1272
- {t}
1273
- </p>,
1274
- );
1275
- };
1276
- const codeRegex = /```([\w-]*)\n?([\s\S]*?)```/g;
1277
- let lastIndex = 0;
1278
- let match: RegExpExecArray | null;
1279
-
1280
- while ((match = codeRegex.exec(trimmedBlock)) !== null) {
1281
- const [full, lang, code] = match;
1282
- const before = trimmedBlock.slice(lastIndex, match.index).trim();
1283
- pushTextBlock(before, `${index}-before-${lastIndex}`);
1284
-
1285
- const language = (lang || '').trim() || 'code';
1286
- // Clean up the code content properly
1287
- const cleanCode = code
1288
- .replace(/^\s+/, '') // Remove leading whitespace
1289
- .replace(/\s+$/, '') // Remove trailing whitespace
1290
- .split('\n')
1291
- .map((line) => line.trim())
1292
- .filter((line) => line.length > 0)
1293
- .join('\n');
1294
-
1295
- parts.push(
1296
- <div
1297
- key={`${index}-code-${match.index}`}
1298
- className="rounded-xl border border-gray-200 bg-gray-50 overflow-hidden shadow-sm"
1299
- >
1300
- <div className="px-4 py-2 text-sm font-bold text-gray-900 bg-gray-100 border-b border-gray-200">
1301
- {language}
1302
- </div>
1303
- <div className="px-4 py-4 overflow-x-auto bg-white">
1304
- <pre className="text-sm text-gray-900 font-mono leading-relaxed whitespace-pre-wrap not-prose">
1305
- <code>{cleanCode}</code>
1306
- </pre>
1307
- </div>
1308
- </div>,
1309
- );
1310
- lastIndex = match.index + full.length;
1311
- }
1312
-
1313
- const after = trimmedBlock.slice(lastIndex).trim();
1314
- pushTextBlock(after, `${index}-after`);
1315
-
1316
- return (
1317
- <div key={index} className="space-y-3">
1318
- {parts}
1319
- </div>
1320
- );
1321
- }
1322
-
1323
- // Raw HTML support per block
1324
- if (isProbablyHTML(trimmedBlock)) {
1325
- return (
1326
- <div
1327
- key={index}
1328
- className="prose prose-sm max-w-none text-gray-800"
1329
- dangerouslySetInnerHTML={{ __html: sanitizeHtml(trimmedBlock) }}
1330
- />
1331
- );
1332
- }
1333
-
1334
- // Check for different content types
1335
- if (trimmedBlock.startsWith('# ')) {
1336
- // Main heading
1337
- return (
1338
- <h1
1339
- key={index}
1340
- className="text-2xl font-bold text-gray-900 mb-4 bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent"
1341
- >
1342
- {trimmedBlock.substring(2)}
1343
- </h1>
1344
- );
1345
- } else if (trimmedBlock.startsWith('## ')) {
1346
- // Secondary heading
1347
- return (
1348
- <h2
1349
- key={index}
1350
- className="text-xl font-semibold text-gray-800 mb-3 border-b border-gray-200 pb-2"
1351
- >
1352
- {trimmedBlock.substring(3)}
1353
- </h2>
1354
- );
1355
- } else if (trimmedBlock.startsWith('### ')) {
1356
- // Tertiary heading
1357
- return (
1358
- <h3 key={index} className="text-lg font-medium text-gray-700 mb-2">
1359
- {trimmedBlock.substring(4)}
1360
- </h3>
1361
- );
1362
- } else if (trimmedBlock.startsWith('- ') || trimmedBlock.startsWith('• ')) {
1363
- // Bullet points
1364
- const items = trimmedBlock.split('\n').filter((line) => line.trim());
1365
- return (
1366
- // <div key={index} className="bg-blue-50 border-l-4 border-blue-500 p-4 rounded-r-lg">
1367
- <div key={index} className=" p-4 rounded-r-lg">
1368
- <ul className="space-y-2">
1369
- {items.map((item, itemIndex) => (
1370
- <li key={itemIndex} className="flex items-start space-x-3 items-center">
1371
- <svg
1372
- className="w-5 h-5 text-black mt-0.5 flex-shrink-0"
1373
- fill="currentColor"
1374
- viewBox="0 0 20 20"
1375
- >
1376
- <path
1377
- fillRule="evenodd"
1378
- d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
1379
- clipRule="evenodd"
1380
- />
1381
- </svg>
1382
- <span className="text-gray-700 leading-relaxed">
1383
- {item
1384
- .replace(/^[-•]\s*/, '')
1385
- .split(/(\*\*.*?\*\*)/)
1386
- .map((part, partIndex) => {
1387
- if (part.match(/\*\*.*?\*\*/)) {
1388
- // Extract text between ** markers and make it bold
1389
- const boldText = part.replace(/\*\*/g, '');
1390
- return (
1391
- <strong
1392
- key={partIndex}
1393
- className="font-semibold text-gray-900"
1394
- >
1395
- {boldText}
1396
- </strong>
1397
- );
1398
- }
1399
- return part;
1400
- })}
1401
- </span>
1402
- </li>
1403
- ))}
1404
- </ul>
1405
- </div>
1406
- );
1407
- } else if (trimmedBlock.startsWith('```')) {
1408
- // Code block
1409
- const codeMatch = trimmedBlock.match(/^```([\w-]*)\n?([\s\S]*?)```$/);
1410
- if (codeMatch) {
1411
- const [, language, codeContent] = codeMatch;
1412
- const lang = (language || '').trim() || 'code';
1413
- // Clean up the code content properly
1414
- const cleanCode = codeContent
1415
- .replace(/^\s+/, '') // Remove leading whitespace
1416
- .replace(/\s+$/, '') // Remove trailing whitespace
1417
- .split('\n')
1418
- .map((line) => line.trim())
1419
- .filter((line) => line.length > 0)
1420
- .join('\n');
1421
-
1422
- return (
1423
- <div
1424
- key={index}
1425
- className="rounded-xl border border-gray-200 bg-gray-50 overflow-hidden shadow-sm"
1426
- >
1427
- <div className="px-4 py-2 text-sm font-bold text-gray-900 bg-gray-100 border-b border-gray-200">
1428
- {lang}
1429
- </div>
1430
- <div className="px-4 py-4 overflow-x-auto bg-white">
1431
- <pre className="text-sm text-gray-900 font-mono leading-relaxed whitespace-pre-wrap not-prose">
1432
- <code>{cleanCode}</code>
1433
- </pre>
1434
- </div>
1435
- </div>
1436
- );
1437
- }
1438
- // Fallback for malformed code blocks
1439
- const codeContent = trimmedBlock.replace(/^```[\w]*\n?/, '').replace(/```$/, '');
1440
- // If the fenced code actually looks like plain bullet text, render as text instead of code
1441
- const hyphenLines = codeContent.split('\n').filter((l) => /^[-•]/.test(l.trim())).length;
1442
- const hasCodeSymbols =
1443
- /[{}<>;=]/.test(codeContent) || /\b(function|class|import|export)\b/.test(codeContent);
1444
- if (hyphenLines >= 2 && !hasCodeSymbols) {
1445
- return (
1446
- <p key={index} className="text-gray-700 leading-relaxed whitespace-pre-wrap">
1447
- {codeContent}
1448
- </p>
1449
- );
1450
- }
1451
-
1452
- return (
1453
- <div
1454
- key={index}
1455
- className="rounded-xl border border-gray-200 bg-gray-50 overflow-hidden shadow-sm"
1456
- >
1457
- <div className="px-4 py-2 text-sm font-bold text-gray-900 bg-gray-100 border-b border-gray-200">
1458
- code
1459
- </div>
1460
- <div className="px-4 py-4 overflow-x-auto bg-white">
1461
- <pre className="text-sm text-gray-900 font-mono leading-relaxed whitespace-pre-wrap not-prose">
1462
- <code>{codeContent}</code>
1463
- </pre>
1464
- </div>
1465
- </div>
1466
- );
1467
- } else if (trimmedBlock.startsWith('🔧 ')) {
1468
- // Tool status message - render as bordered container
1469
- const toolText = trimmedBlock.substring(2); // Remove the 🔧 emoji
1470
- return (
1471
- <div key={index} className="mt-2">
1472
- <div className="inline-block px-3 py-1 bg-gray-100 border border-gray-300 rounded-lg text-sm text-gray-700">
1473
- <span className="text-green-500 mr-2">🔧</span>
1474
- {toolText}
1475
- </div>
1476
- </div>
1477
- );
1478
- } else if (trimmedBlock.includes('**') || trimmedBlock.includes('`')) {
1479
- // Inline formatting (bold, inline code, highlights)
1480
- const formattedContent = trimmedBlock
1481
- .replace(
1482
- /\*\*(.*?)\*\*/g,
1483
- '<span class="font-semibold text-gray-900 bg-yellow-100 px-1 py-0.5 rounded">$1</span>',
1484
- )
1485
- .replace(
1486
- /`([^`]+)`/g,
1487
- '<code class="bg-gray-100 px-2 py-1 rounded text-sm font-mono text-gray-800">$1</code>',
1488
- );
1489
-
1490
- return (
1491
- <div
1492
- key={index}
1493
- className="text-gray-700 leading-relaxed"
1494
- dangerouslySetInnerHTML={{ __html: sanitizeHtml(formattedContent) }}
1495
- />
1496
- );
1497
- } else if (isCodeBlock(trimmedBlock)) {
1498
- // Code block detection for content that looks like code
1499
- // Clean up the code content for better display
1500
- const cleanCode = trimmedBlock
1501
- .replace(/^\s+/, '') // Remove leading whitespace
1502
- .replace(/\s+$/, '') // Remove trailing whitespace
1503
- .split('\n')
1504
- .map((line) => line.trim())
1505
- .filter((line) => line.length > 0)
1506
- .join('\n');
1507
-
1508
- // Heuristic: if the "code" contains many leading hyphen bullets and no typical code symbols, treat as text
1509
- const hyphenLines = cleanCode.split('\n').filter((l) => /^[-•]/.test(l.trim())).length;
1510
- const hasCodeSymbols =
1511
- /[{}<>;=]/.test(cleanCode) || /\b(function|class|import|export)\b/.test(cleanCode);
1512
- if (hyphenLines >= 2 && !hasCodeSymbols) {
1513
- return (
1514
- <p key={index} className="text-gray-700 leading-relaxed whitespace-pre-wrap">
1515
- {trimmedBlock}
1516
- </p>
1517
- );
1518
- }
1519
-
1520
- return (
1521
- <div key={index} className="rounded-xl border border-gray-200 bg-gray-50 overflow-hidden">
1522
- <div className="px-4 py-2 text-sm font-bold text-gray-900">typescript</div>
1523
- <div className="px-4 pb-4 overflow-x-auto">
1524
- <pre className="text-sm text-gray-900 font-mono leading-relaxed whitespace-pre-wrap not-prose">
1525
- <code>{cleanCode}</code>
1526
- </pre>
1527
- </div>
1528
- </div>
1529
- );
1530
- } else if (
1531
- // Fallback: More aggressive code detection for specific patterns
1532
- trimmedBlock.includes('export default') ||
1533
- trimmedBlock.includes('function') ||
1534
- trimmedBlock.includes('return (') ||
1535
- trimmedBlock.includes('className=') ||
1536
- trimmedBlock.includes('Route::') ||
1537
- trimmedBlock.includes('class Post') ||
1538
- trimmedBlock.includes('extends Model') ||
1539
- trimmedBlock.includes('extends Controller') ||
1540
- (trimmedBlock.includes('{') && trimmedBlock.includes('}') && trimmedBlock.includes(';'))
1541
- ) {
1542
- console.log('Fallback code detection triggered for block:', trimmedBlock.substring(0, 100) + '...');
1543
-
1544
- // Clean up the code content for better display
1545
- const cleanCode = trimmedBlock
1546
- .replace(/^\s+/, '') // Remove leading whitespace
1547
- .replace(/\s+$/, '') // Remove trailing whitespace
1548
- .split('\n')
1549
- .map((line) => line.trim())
1550
- .filter((line) => line.length > 0)
1551
- .join('\n');
1552
-
1553
- return (
1554
- <div
1555
- key={index}
1556
- className="rounded-xl border border-gray-200 bg-gray-50 overflow-hidden shadow-sm"
1557
- >
1558
- <div className="px-4 py-2 text-sm font-bold text-gray-900 bg-gray-100 border-b border-gray-200">
1559
- code
1560
- </div>
1561
- <div className="px-4 py-4 overflow-x-auto bg-white">
1562
- <pre className="text-sm text-gray-900 font-mono leading-relaxed whitespace-pre-wrap not-prose">
1563
- <code>{cleanCode}</code>
1564
- </pre>
1565
- </div>
1566
- </div>
1567
- );
1568
- } else {
1569
- // Regular paragraph
1570
- return (
1571
- <p key={index} className="text-gray-700 leading-relaxed whitespace-pre-wrap">
1572
- {trimmedBlock}
1573
- </p>
1574
- );
1575
- }
1576
- })}
769
+ <div className="message-container">
770
+ <p className="text-gray-700 leading-relaxed whitespace-pre-wrap">{content}</p>
1577
771
  </div>
1578
772
  );
1579
773
  };
@@ -1760,6 +954,8 @@ const ModernMessageBubble: React.FC<ModernMessageBubbleProps> = ({
1760
954
  onMessageClick?.(message);
1761
955
  };
1762
956
 
957
+ const isAssistantRole = (message as any)?.propsConfiguration?.contents?.role === 'ASSISTANT';
958
+
1763
959
  // For user messages, create a right-aligned layout with avatar and name
1764
960
  if (isOwnMessage) {
1765
961
  const authorName =
@@ -1768,22 +964,30 @@ const ModernMessageBubble: React.FC<ModernMessageBubbleProps> = ({
1768
964
  : message.author?.username || 'You';
1769
965
 
1770
966
  return (
1771
- <div className="py-1 hover:bg-gray-50 hover:bg-opacity-50 rounded px-1 sm:px-2 -mx-1 sm:-mx-2 group">
967
+ <div
968
+ className={`py-1 hover:bg-gray-50 hover:bg-opacity-50 rounded px-1 sm:px-2 -mx-1 sm:-mx-2 group ${
969
+ isAssistantRole ? 'border border-gray-200' : ''
970
+ }`}
971
+ >
1772
972
  <div className="flex items-start justify-end gap-2">
1773
973
  {/* Message content and timestamp on the left */}
1774
974
  <div className="flex flex-col items-end max-w-xs lg:max-w-md">
1775
- <div className="flex items-end space-x-2 mb-0.5">
1776
- <span className="text-sm font-semibold text-gray-900">{authorName}</span>
1777
- </div>
975
+ {!isAssistantRole && (
976
+ <div className="flex items-end space-x-2 mb-0.5">
977
+ <span className="text-sm font-semibold text-gray-900">{authorName}</span>
978
+ </div>
979
+ )}
1778
980
 
1779
981
  <div
1780
- className="text-sm text-gray-900 cursor-pointer hover:bg-gray-100 px-1 sm:px-2 py-1 rounded"
1781
- onClick={handleClick}
982
+ className={`text-sm text-gray-900 ${
983
+ isAssistantRole ? 'cursor-pointer' : ''
984
+ } px-1 sm:px-2 py-1 rounded`}
985
+ onClick={isAssistantRole ? handleClick : undefined}
1782
986
  dir={detectTextDirection((message as any)?.message || '')}
1783
987
  lang={detectLanguageTag((message as any)?.message || '')}
1784
988
  >
1785
989
  {message.message && (
1786
- <div className="prose prose-sm max-w-none">
990
+ <div className="max-w-none">
1787
991
  {isProbablyHTML(message.message) ? (
1788
992
  <div
1789
993
  className="text-gray-800 html-content"
@@ -1818,17 +1022,21 @@ const ModernMessageBubble: React.FC<ModernMessageBubbleProps> = ({
1818
1022
  </div>
1819
1023
 
1820
1024
  {/* User avatar on the right */}
1821
- <div className="flex-shrink-0 mt-0.5">
1822
- <img
1823
- className="w-8 h-8 sm:w-10 sm:h-10 rounded-lg cursor-pointer hover:opacity-80 transition-opacity object-cover"
1824
- src={message.author?.picture || '/default-avatar.svg'}
1825
- alt={authorName}
1826
- onClick={handleClick}
1827
- onError={(e) => {
1828
- e.currentTarget.src = '/default-avatar.svg';
1829
- }}
1830
- />
1831
- </div>
1025
+ {!isAssistantRole && (
1026
+ <div className="flex-shrink-0 mt-0.5">
1027
+ <img
1028
+ className={`w-8 h-8 sm:w-10 sm:h-10 rounded-lg ${
1029
+ isAssistantRole ? '' : 'cursor-pointer hover:opacity-80'
1030
+ } transition-opacity object-cover`}
1031
+ src={message.author?.picture || '/default-avatar.svg'}
1032
+ alt={authorName}
1033
+ onClick={isAssistantRole ? undefined : handleClick}
1034
+ onError={(e) => {
1035
+ e.currentTarget.src = '/default-avatar.svg';
1036
+ }}
1037
+ />
1038
+ </div>
1039
+ )}
1832
1040
  </div>
1833
1041
  </div>
1834
1042
  );
@@ -1838,17 +1046,15 @@ const ModernMessageBubble: React.FC<ModernMessageBubbleProps> = ({
1838
1046
  return (
1839
1047
  <div
1840
1048
  className={`group/message transition-all duration-200 hover:bg-gray-50 rounded-lg px-3 py-1 -mx-3 ${
1841
- isSystemMessage ? '' : 'cursor-pointer'
1049
+ isSystemMessage || isAssistantRole ? '' : 'cursor-pointer'
1842
1050
  }`}
1843
- onClick={isSystemMessage ? undefined : handleClick}
1051
+ onClick={isSystemMessage || isAssistantRole ? undefined : handleClick}
1844
1052
  >
1845
1053
  <div className="flex items-start justify-between gap-3">
1846
1054
  <div className="flex-1 min-w-0">
1847
1055
  {message.message && (
1848
1056
  <div
1849
- className={`prose prose-sm max-w-none ${
1850
- isSystemMessage ? 'text-gray-800' : 'text-gray-900'
1851
- }`}
1057
+ className={`max-w-none ${isSystemMessage ? 'text-gray-800' : 'text-gray-900'}`}
1852
1058
  dir={detectTextDirection((message as any)?.message || '')}
1853
1059
  lang={detectLanguageTag((message as any)?.message || '')}
1854
1060
  >
@@ -1857,7 +1063,7 @@ const ModernMessageBubble: React.FC<ModernMessageBubbleProps> = ({
1857
1063
  <FormattedMessageContent content={message.message} />
1858
1064
  ) : isProbablyHTML(message.message) ? (
1859
1065
  <div
1860
- className="prose prose-sm max-w-none text-gray-800 html-content"
1066
+ className="max-w-none text-gray-800 html-content"
1861
1067
  dangerouslySetInnerHTML={{
1862
1068
  __html: prettifyHtmlContent(sanitizeHtml(message.message)),
1863
1069
  }}
@@ -1936,6 +1142,10 @@ export const ModernMessageGroupComponent: React.FC<ModernMessageGroupProps> = ({
1936
1142
  onMessageClick,
1937
1143
  isDesktopView = false,
1938
1144
  isSmallScreen = false,
1145
+ sandboxErrors = [],
1146
+ currentFiles = {},
1147
+ onFixError,
1148
+ onRecreateSandbox,
1939
1149
  }) => {
1940
1150
  // Inject CSS styles for HTML content
1941
1151
  useInjectStyles();
@@ -1963,6 +1173,17 @@ export const ModernMessageGroupComponent: React.FC<ModernMessageGroupProps> = ({
1963
1173
  />
1964
1174
  );
1965
1175
  })}
1176
+
1177
+ {sandboxErrors?.map((error) => (
1178
+ <div key={error.id} className="px-2">
1179
+ <ErrorFixCard
1180
+ error={error}
1181
+ onFixError={onFixError || (() => Promise.resolve())}
1182
+ currentFiles={currentFiles}
1183
+ isFixing={false}
1184
+ />
1185
+ </div>
1186
+ ))}
1966
1187
  </div>
1967
1188
  );
1968
1189
  };