@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.
- package/CHANGELOG.md +4 -0
- package/lib/components/AIAgent/AIAgent.d.ts +7 -0
- package/lib/components/AIAgent/AIAgent.d.ts.map +1 -1
- package/lib/components/AIAgent/AIAgent.js +362 -615
- package/lib/components/AIAgent/AIAgent.js.map +1 -1
- package/lib/components/InboxMessage/InputComponent.d.ts.map +1 -1
- package/lib/components/InboxMessage/InputComponent.js +143 -140
- package/lib/components/InboxMessage/InputComponent.js.map +1 -1
- package/lib/components/InboxMessage/RightSidebarAi.d.ts +23 -0
- package/lib/components/InboxMessage/RightSidebarAi.d.ts.map +1 -0
- package/lib/components/InboxMessage/RightSidebarAi.js +9 -0
- package/lib/components/InboxMessage/RightSidebarAi.js.map +1 -0
- package/lib/components/InboxMessage/index.d.ts +1 -0
- package/lib/components/InboxMessage/index.d.ts.map +1 -1
- package/lib/components/InboxMessage/message-widgets/ErrorFixCard.d.ts +11 -0
- package/lib/components/InboxMessage/message-widgets/ErrorFixCard.d.ts.map +1 -0
- package/lib/components/InboxMessage/message-widgets/ErrorFixCard.js +194 -0
- package/lib/components/InboxMessage/message-widgets/ErrorFixCard.js.map +1 -0
- package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.d.ts +5 -1
- package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.d.ts.map +1 -1
- package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.js +308 -857
- package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.js.map +1 -1
- package/lib/components/ModelConfigPanel.d.ts +12 -0
- package/lib/components/ModelConfigPanel.d.ts.map +1 -0
- package/lib/components/ModelConfigPanel.js +304 -0
- package/lib/components/ModelConfigPanel.js.map +1 -0
- package/lib/components/filler-components/RightSiderBar.d.ts +24 -0
- package/lib/components/filler-components/RightSiderBar.d.ts.map +1 -0
- package/lib/components/filler-components/RightSiderBar.js +335 -0
- package/lib/components/filler-components/RightSiderBar.js.map +1 -0
- package/lib/components/index.d.ts +4 -2
- package/lib/components/index.d.ts.map +1 -1
- package/lib/components/live-code-editor/hybrid-live-editor.d.ts +20 -0
- package/lib/components/live-code-editor/hybrid-live-editor.d.ts.map +1 -0
- package/lib/components/live-code-editor/hybrid-live-editor.js +68 -0
- package/lib/components/live-code-editor/hybrid-live-editor.js.map +1 -0
- package/lib/components/live-code-editor/index.d.ts +4 -0
- package/lib/components/live-code-editor/index.d.ts.map +1 -0
- package/lib/components/live-code-editor/live-code-editor.d.ts +14 -0
- package/lib/components/live-code-editor/live-code-editor.d.ts.map +1 -0
- package/lib/components/live-code-editor/live-code-editor.js +207 -0
- package/lib/components/live-code-editor/live-code-editor.js.map +1 -0
- package/lib/components/slot-fill/chat-message-filler.js +1 -1
- package/lib/components/slot-fill/chat-message-filler.js.map +1 -1
- package/lib/components/slot-fill/index.d.ts +1 -0
- package/lib/components/slot-fill/index.d.ts.map +1 -1
- package/lib/components/slot-fill/right-sidebar-filler.d.ts +4 -0
- package/lib/components/slot-fill/right-sidebar-filler.d.ts.map +1 -0
- package/lib/components/slot-fill/right-sidebar-filler.js +13 -0
- package/lib/components/slot-fill/right-sidebar-filler.js.map +1 -0
- package/lib/components/ui/button.d.ts +9 -0
- package/lib/components/ui/button.d.ts.map +1 -0
- package/lib/compute.js +1 -2
- package/lib/container/AiInbox.d.ts.map +1 -1
- package/lib/container/AiLandingInput.d.ts.map +1 -1
- package/lib/container/AiLandingInput.js +46 -119
- package/lib/container/AiLandingInput.js.map +1 -1
- package/lib/container/Inbox.js +1 -1
- package/lib/container/Inbox.js.map +1 -1
- package/lib/container/InboxAiMessagesLoader.d.ts +0 -21
- package/lib/container/InboxAiMessagesLoader.d.ts.map +1 -1
- package/lib/container/InboxAiMessagesLoader.js +18 -32
- package/lib/container/InboxAiMessagesLoader.js.map +1 -1
- package/lib/container/ServiceInbox.js +1 -1
- package/lib/container/ServiceInbox.js.map +1 -1
- package/lib/container/ThreadMessages.js +1 -1
- package/lib/container/ThreadMessages.js.map +1 -1
- package/lib/container/ThreadMessagesInbox.js +1 -1
- package/lib/container/ThreadMessagesInbox.js.map +1 -1
- package/lib/container/Threads.js +1 -1
- package/lib/container/Threads.js.map +1 -1
- package/lib/container/index.d.ts +2 -1
- package/lib/container/index.d.ts.map +1 -1
- package/lib/enums/messenger-slot-fill-name-enum.d.ts +2 -1
- package/lib/enums/messenger-slot-fill-name-enum.d.ts.map +1 -1
- package/lib/enums/messenger-slot-fill-name-enum.js +1 -0
- package/lib/enums/messenger-slot-fill-name-enum.js.map +1 -1
- package/lib/hooks/index.d.ts +3 -0
- package/lib/hooks/index.d.ts.map +1 -0
- package/lib/hooks/use-file-sync.d.ts +16 -0
- package/lib/hooks/use-file-sync.d.ts.map +1 -0
- package/lib/hooks/use-file-sync.js +63 -0
- package/lib/hooks/use-file-sync.js.map +1 -0
- package/lib/hooks/usePersistentModelConfig.d.ts +15 -0
- package/lib/hooks/usePersistentModelConfig.d.ts.map +1 -0
- package/lib/hooks/usePersistentModelConfig.js +46 -0
- package/lib/hooks/usePersistentModelConfig.js.map +1 -0
- package/lib/index.d.ts +4 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/machines/aiAgentMachine.d.ts.map +1 -1
- package/lib/machines/aiAgentMachine.js +64 -21
- package/lib/machines/aiAgentMachine.js.map +1 -1
- package/lib/machines/aiAgentMachine.simple.d.ts +3 -0
- package/lib/machines/aiAgentMachine.simple.d.ts.map +1 -0
- package/lib/machines/aiAgentMachine.simple.js +108 -0
- package/lib/machines/aiAgentMachine.simple.js.map +1 -0
- package/lib/machines/index.d.ts +3 -0
- package/lib/machines/index.d.ts.map +1 -0
- package/lib/module.d.ts +2 -1
- package/lib/module.d.ts.map +1 -1
- package/lib/module.js +11 -3
- package/lib/module.js.map +1 -1
- package/lib/routes.json +1 -2
- package/lib/templates/InboxWithAi.d.ts.map +1 -1
- package/lib/templates/InboxWithAi.js +129 -70
- package/lib/templates/InboxWithAi.js.map +1 -1
- package/lib/templates/InboxWithAi.tsx +151 -90
- package/lib/utils/utils.d.ts +2 -0
- package/lib/utils/utils.d.ts.map +1 -0
- package/lib/utils/utils.js +3 -0
- package/lib/utils/utils.js.map +1 -0
- package/package.json +8 -5
- package/src/components/AIAgent/AIAgent.tsx +469 -731
- package/src/components/AIAgent/AIAgent.tsx.bk +1365 -0
- package/src/components/InboxMessage/InputComponent.tsx +2 -1
- package/src/components/InboxMessage/RightSidebarAi.tsx +37 -0
- package/src/components/InboxMessage/index.ts +1 -0
- package/src/components/InboxMessage/message-widgets/ErrorFixCard.tsx +240 -0
- package/src/components/InboxMessage/message-widgets/ModernMessageGroup.tsx +337 -1116
- package/src/components/ModelConfigPanel.tsx +334 -0
- package/src/components/filler-components/RightSiderBar.tsx +408 -0
- package/src/components/index.ts +4 -1
- package/src/components/live-code-editor/hybrid-live-editor.tsx +105 -0
- package/src/components/live-code-editor/index.ts +3 -0
- package/src/components/live-code-editor/live-code-editor.tsx +257 -0
- package/src/components/slot-fill/index.ts +1 -0
- package/src/components/slot-fill/right-sidebar-filler.tsx +39 -0
- package/src/components/ui/button.tsx +32 -0
- package/src/container/AiInbox.tsx +26 -3
- package/src/container/AiLandingInput.tsx +48 -22
- package/src/container/InboxAiMessagesLoader.tsx +17 -31
- package/src/container/index.ts +2 -0
- package/src/enums/messenger-slot-fill-name-enum.ts +1 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/use-file-sync.ts +91 -0
- package/src/hooks/usePersistentModelConfig.ts +63 -0
- package/src/index.ts +7 -0
- package/src/machines/aiAgentMachine.simple.ts +89 -0
- package/src/machines/aiAgentMachine.ts +67 -19
- package/src/machines/aiAgentMachine.ts.bk +1296 -0
- package/src/machines/index.ts +2 -0
- package/src/module.tsx +10 -1
- package/src/templates/InboxWithAi.tsx +151 -90
- package/src/utils/utils.ts +3 -0
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import React, { useEffect } from 'react';
|
|
2
|
-
import { format,
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
.
|
|
220
|
-
|
|
221
|
-
margin-bottom: 1rem;
|
|
198
|
+
.markdown-content h1 {
|
|
199
|
+
font-size: 1.5rem;
|
|
222
200
|
font-weight: 700;
|
|
223
|
-
|
|
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
|
-
.
|
|
208
|
+
.markdown-content h2 {
|
|
240
209
|
font-size: 1.25rem;
|
|
241
|
-
|
|
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
|
-
|
|
258
|
-
|
|
213
|
+
color: #1f2937;
|
|
214
|
+
padding-bottom: 0.375rem;
|
|
215
|
+
border-bottom: 1px solid #e5e7eb;
|
|
259
216
|
}
|
|
260
217
|
|
|
261
|
-
.
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
-
.
|
|
270
|
-
|
|
226
|
+
.markdown-content p {
|
|
227
|
+
margin-bottom: 0.75rem;
|
|
228
|
+
line-height: 1.6;
|
|
229
|
+
color: #4b5563;
|
|
271
230
|
}
|
|
272
231
|
|
|
273
|
-
.
|
|
274
|
-
|
|
232
|
+
.markdown-content ul, .markdown-content ol {
|
|
233
|
+
margin: 1rem 0;
|
|
234
|
+
padding-left: 1.5rem;
|
|
275
235
|
}
|
|
276
236
|
|
|
277
|
-
.
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
.
|
|
242
|
+
.markdown-content code {
|
|
286
243
|
background-color: #f3f4f6;
|
|
287
|
-
padding: 0.
|
|
288
|
-
border-radius: 0.
|
|
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
|
-
.
|
|
296
|
-
|
|
297
|
-
|
|
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.
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
-
.
|
|
270
|
+
.markdown-content blockquote {
|
|
310
271
|
border-left: 4px solid #3b82f6;
|
|
311
|
-
background:
|
|
312
|
-
padding:
|
|
313
|
-
margin:
|
|
314
|
-
border-radius:
|
|
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
|
-
|
|
278
|
+
border-top: 1px solid #e5e7eb;
|
|
279
|
+
border-right: 1px solid #e5e7eb;
|
|
280
|
+
border-bottom: 1px solid #e5e7eb;
|
|
318
281
|
}
|
|
319
282
|
|
|
320
|
-
.
|
|
283
|
+
.markdown-content table {
|
|
321
284
|
border-collapse: collapse;
|
|
322
|
-
border:
|
|
285
|
+
border: 1px solid #e5e7eb;
|
|
323
286
|
width: 100%;
|
|
324
|
-
margin:
|
|
325
|
-
border-radius:
|
|
287
|
+
margin: 1rem 0;
|
|
288
|
+
border-radius: 8px;
|
|
326
289
|
overflow: hidden;
|
|
327
|
-
box-shadow: 0
|
|
290
|
+
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
|
|
328
291
|
}
|
|
329
292
|
|
|
330
|
-
.
|
|
331
|
-
background:
|
|
332
|
-
color:
|
|
333
|
-
font-weight:
|
|
334
|
-
padding:
|
|
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:
|
|
299
|
+
border-bottom: 1px solid #e5e7eb;
|
|
300
|
+
font-size: 0.875rem;
|
|
337
301
|
}
|
|
338
302
|
|
|
339
|
-
.
|
|
303
|
+
.markdown-content td {
|
|
340
304
|
border: 1px solid #e5e7eb;
|
|
341
|
-
padding:
|
|
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
|
-
.
|
|
312
|
+
.markdown-content tr:nth-child(even) td {
|
|
348
313
|
background-color: #f9fafb;
|
|
349
314
|
}
|
|
350
315
|
|
|
351
|
-
.
|
|
316
|
+
.markdown-content tr:hover td {
|
|
352
317
|
background-color: #f3f4f6;
|
|
353
318
|
}
|
|
354
319
|
|
|
355
|
-
/* Enhanced
|
|
356
|
-
.
|
|
357
|
-
|
|
358
|
-
|
|
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
|
-
.
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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
|
-
.
|
|
380
|
-
|
|
347
|
+
.code-block-container {
|
|
348
|
+
background: #ffffff;
|
|
349
|
+
border: 1px solid #e5e7eb;
|
|
350
|
+
border-radius: 8px;
|
|
381
351
|
overflow: hidden;
|
|
382
|
-
|
|
383
|
-
|
|
352
|
+
margin: 1rem 0;
|
|
353
|
+
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
|
|
384
354
|
}
|
|
385
355
|
|
|
386
|
-
.
|
|
387
|
-
background:
|
|
388
|
-
padding: 0.
|
|
389
|
-
border-bottom: 1px solid #
|
|
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
|
-
.
|
|
398
|
-
|
|
399
|
-
padding: 1.5rem;
|
|
367
|
+
.code-block-content {
|
|
368
|
+
padding: 1rem;
|
|
400
369
|
overflow-x: auto;
|
|
401
370
|
}
|
|
402
|
-
`;
|
|
403
371
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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="
|
|
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
|
-
<
|
|
704
|
-
{JSON.stringify(block, null, 2)}
|
|
705
|
-
</
|
|
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
|
|
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
|
-
//
|
|
785
|
-
|
|
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="
|
|
814
|
-
<div className="
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
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
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
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
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
{
|
|
905
|
-
<
|
|
906
|
-
<
|
|
907
|
-
className="w-
|
|
908
|
-
|
|
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
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
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
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
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
|
-
//
|
|
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="
|
|
1111
|
-
|
|
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
|
|
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
|
-
|
|
1776
|
-
<
|
|
1777
|
-
|
|
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=
|
|
1781
|
-
|
|
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="
|
|
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
|
-
|
|
1822
|
-
<
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
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={`
|
|
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="
|
|
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
|
};
|