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