@sybilion/uilib 1.2.21 → 1.2.22
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/dist/esm/components/ui/Chat/ChatMessage/AgentMessageContent.helpers.js +120 -9
- package/dist/esm/components/ui/Chat/ChatMessage/ChatMessage.js +2 -1
- package/dist/esm/components/ui/InteractiveContent/InteractiveContent.styl.js +1 -1
- package/dist/esm/types/src/components/ui/Chat/ChatMessage/AgentMessageContent.helpers.d.ts +7 -1
- package/package.json +1 -1
- package/src/components/ui/Chat/ChatMessage/AgentMessageContent.helpers.tsx +152 -8
- package/src/components/ui/Chat/ChatMessage/ChatMessage.tsx +5 -1
- package/src/components/ui/InteractiveContent/InteractiveContent.styl +17 -0
|
@@ -56,6 +56,20 @@ const injectBold = (content) => {
|
|
|
56
56
|
length: matches[0].length,
|
|
57
57
|
};
|
|
58
58
|
};
|
|
59
|
+
/** `_italic_` (underscore) — avoids clashing with `* ` bullet markers. */
|
|
60
|
+
const injectItalic = (content) => {
|
|
61
|
+
const matches = content.match(/(?<![A-Za-z0-9])_([^_\n]+?)_(?![A-Za-z0-9])/);
|
|
62
|
+
if (!matches || matches.index === undefined)
|
|
63
|
+
return null;
|
|
64
|
+
if (isInsideHtmlListOrTable(content, matches.index)) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
elem: jsx("em", { children: matches[1] }),
|
|
69
|
+
index: matches.index,
|
|
70
|
+
length: matches[0].length,
|
|
71
|
+
};
|
|
72
|
+
};
|
|
59
73
|
const injectBullet = (content) => {
|
|
60
74
|
// Match bullet points: * or - followed by space
|
|
61
75
|
// Match at start of string/line or after whitespace/colon (for nested bullets)
|
|
@@ -229,6 +243,33 @@ const isAllowedHref = (href) => {
|
|
|
229
243
|
return true;
|
|
230
244
|
return false;
|
|
231
245
|
};
|
|
246
|
+
const normalizeWwwToHttps = (raw) => {
|
|
247
|
+
const t = raw.trim();
|
|
248
|
+
if (/^www\./i.test(t))
|
|
249
|
+
return `https://${t}`;
|
|
250
|
+
return t;
|
|
251
|
+
};
|
|
252
|
+
/** Strip ASCII closing punctuation often pasted after URLs. */
|
|
253
|
+
const stripTrailingUrlPunctuation = (s) => {
|
|
254
|
+
let u = s;
|
|
255
|
+
while (u.length > 0 && /[.,;:!?)}\]]$/u.test(u)) {
|
|
256
|
+
u = u.slice(0, -1);
|
|
257
|
+
}
|
|
258
|
+
return u;
|
|
259
|
+
};
|
|
260
|
+
function linkTargetRelForHref(href, explicitTarget) {
|
|
261
|
+
if (explicitTarget) {
|
|
262
|
+
const t = explicitTarget.trim();
|
|
263
|
+
if (t.toLowerCase() === '_blank') {
|
|
264
|
+
return { target: '_blank', rel: 'noopener noreferrer' };
|
|
265
|
+
}
|
|
266
|
+
return { target: t || undefined, rel: undefined };
|
|
267
|
+
}
|
|
268
|
+
if (/^https?:\/\//i.test(href) || /^mailto:/i.test(href)) {
|
|
269
|
+
return { target: '_blank', rel: 'noopener noreferrer' };
|
|
270
|
+
}
|
|
271
|
+
return { target: undefined, rel: undefined };
|
|
272
|
+
}
|
|
232
273
|
const runFormattingPipeline = (text, injectors) => {
|
|
233
274
|
let result = [text];
|
|
234
275
|
try {
|
|
@@ -260,6 +301,76 @@ const runFormattingPipeline = (text, injectors) => {
|
|
|
260
301
|
}
|
|
261
302
|
return result;
|
|
262
303
|
};
|
|
304
|
+
const linkLabelInjectors = [
|
|
305
|
+
injectHTMLTags,
|
|
306
|
+
injectBold,
|
|
307
|
+
injectItalic,
|
|
308
|
+
injectNewlines,
|
|
309
|
+
];
|
|
310
|
+
const injectMarkdownLink = (content) => {
|
|
311
|
+
const matches = content.match(/\[([^\]]+)\]\(([^)]+)\)/);
|
|
312
|
+
if (!matches || matches.index === undefined)
|
|
313
|
+
return null;
|
|
314
|
+
if (isInsideHtmlListOrTable(content, matches.index)) {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
const href = normalizeWwwToHttps(matches[2].trim());
|
|
318
|
+
if (!isAllowedHref(href))
|
|
319
|
+
return null;
|
|
320
|
+
const { target, rel } = linkTargetRelForHref(href, undefined);
|
|
321
|
+
const label = matches[1];
|
|
322
|
+
return {
|
|
323
|
+
elem: (jsx("a", { href: href, target: target, rel: rel, children: runFormattingPipeline(label, linkLabelInjectors) })),
|
|
324
|
+
index: matches.index,
|
|
325
|
+
length: matches[0].length,
|
|
326
|
+
};
|
|
327
|
+
};
|
|
328
|
+
const injectAutolinkUrl = (content) => {
|
|
329
|
+
const candidates = [];
|
|
330
|
+
const reHttp = /https?:\/\/[^\s<>"']+/gi;
|
|
331
|
+
let hm;
|
|
332
|
+
while ((hm = reHttp.exec(content)) !== null) {
|
|
333
|
+
const href = stripTrailingUrlPunctuation(hm[0]);
|
|
334
|
+
if (href.length >= (href.startsWith('https://') ? 8 : 7) &&
|
|
335
|
+
isAllowedHref(href) &&
|
|
336
|
+
/^https?:\/\//i.test(href)) {
|
|
337
|
+
candidates.push({
|
|
338
|
+
index: hm.index,
|
|
339
|
+
length: href.length,
|
|
340
|
+
href,
|
|
341
|
+
display: href,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
const reWww = /(^|[^A-Za-z0-9/])(www\.[^\s<>"']+)/gi;
|
|
346
|
+
while ((hm = reWww.exec(content)) !== null) {
|
|
347
|
+
const prefix = hm[1] ?? '';
|
|
348
|
+
const rawWww = hm[2];
|
|
349
|
+
const body = stripTrailingUrlPunctuation(rawWww);
|
|
350
|
+
const href = `https://${body}`;
|
|
351
|
+
if (body.length > 4 && isAllowedHref(href)) {
|
|
352
|
+
candidates.push({
|
|
353
|
+
index: hm.index + prefix.length,
|
|
354
|
+
length: body.length,
|
|
355
|
+
href,
|
|
356
|
+
display: body,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (candidates.length === 0)
|
|
361
|
+
return null;
|
|
362
|
+
candidates.sort((a, b) => a.index - b.index);
|
|
363
|
+
const c = candidates[0];
|
|
364
|
+
if (isInsideHtmlListOrTable(content, c.index)) {
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
const { target, rel } = linkTargetRelForHref(c.href, undefined);
|
|
368
|
+
return {
|
|
369
|
+
elem: (jsx("a", { href: c.href, target: target, rel: rel, children: c.display })),
|
|
370
|
+
index: c.index,
|
|
371
|
+
length: c.length,
|
|
372
|
+
};
|
|
373
|
+
};
|
|
263
374
|
const injectAnchor = (content) => {
|
|
264
375
|
const regex = /<a\s+([^>]+)>([\s\S]*?)<\/a\s*>/i;
|
|
265
376
|
const matches = content.match(regex);
|
|
@@ -281,15 +392,9 @@ const injectAnchor = (content) => {
|
|
|
281
392
|
if (!href || !isAllowedHref(href)) {
|
|
282
393
|
return null;
|
|
283
394
|
}
|
|
284
|
-
const
|
|
285
|
-
const target = opensNewTab ? '_blank' : targetRaw || undefined;
|
|
286
|
-
const rel = target === '_blank' ? 'noopener noreferrer' : undefined;
|
|
395
|
+
const { target, rel } = linkTargetRelForHref(href, targetRaw || undefined);
|
|
287
396
|
return {
|
|
288
|
-
elem: (jsx("a", { href: href, target: target, rel: rel, children: runFormattingPipeline(inner,
|
|
289
|
-
injectHTMLTags,
|
|
290
|
-
injectBold,
|
|
291
|
-
injectNewlines,
|
|
292
|
-
]) })),
|
|
397
|
+
elem: (jsx("a", { href: href, target: target, rel: rel, children: runFormattingPipeline(inner, linkLabelInjectors) })),
|
|
293
398
|
index: matches.index,
|
|
294
399
|
length: matches[0].length,
|
|
295
400
|
};
|
|
@@ -297,16 +402,22 @@ const injectAnchor = (content) => {
|
|
|
297
402
|
const applyFormatting = (text) => runFormattingPipeline(text, [
|
|
298
403
|
injectHeaders,
|
|
299
404
|
injectAnchor,
|
|
405
|
+
injectMarkdownLink,
|
|
300
406
|
injectHTMLTags,
|
|
301
407
|
injectBullet,
|
|
302
408
|
injectBold,
|
|
409
|
+
injectItalic,
|
|
410
|
+
injectAutolinkUrl,
|
|
303
411
|
injectNewlines,
|
|
304
412
|
]);
|
|
305
413
|
const applyFormattingInline = (text) => runFormattingPipeline(text, [
|
|
306
414
|
injectAnchor,
|
|
415
|
+
injectMarkdownLink,
|
|
307
416
|
injectHTMLTags,
|
|
308
417
|
injectBold,
|
|
418
|
+
injectItalic,
|
|
419
|
+
injectAutolinkUrl,
|
|
309
420
|
injectNewlines,
|
|
310
421
|
]);
|
|
311
422
|
|
|
312
|
-
export { applyFormatting, applyFormattingInline, convertMarkdownTableToHTML, injectAnchor, injectBold, injectBullet, injectHeaders, injectNewlines };
|
|
423
|
+
export { applyFormatting, applyFormattingInline, convertMarkdownTableToHTML, injectAnchor, injectBold, injectBullet, injectHeaders, injectItalic, injectNewlines };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
2
|
import cn from 'classnames';
|
|
3
|
+
import { InteractiveContent } from '../../InteractiveContent/InteractiveContent.js';
|
|
3
4
|
import { TextShimmer } from '../../TextShimmer/TextShimmer.js';
|
|
4
5
|
import { MessageRole, GENERATING_DASHBOARD_SYSTEM_TEXT } from '../Chat.types.js';
|
|
5
6
|
import { AgentMessageContent } from './AgentMessageContent.js';
|
|
@@ -9,7 +10,7 @@ import { UserCsvAttachmentBubble } from './UserCsvAttachmentBubble.js';
|
|
|
9
10
|
function ChatMessage({ role, text, userCsvAttachment, onQuickReply, suppressedQuickReplyKeys, quickReplyDisabled, isLastMessage = true, scriptContinue, onScriptContinue, renderMessageChart, }) {
|
|
10
11
|
const isAssistant = role === MessageRole.ASSISTANT;
|
|
11
12
|
const isSystem = role === MessageRole.SYSTEM;
|
|
12
|
-
return (jsx("div", { className: cn(S.root, S[`role-${role}`]), children: isSystem ? (jsx("div", { className: S.text, children: text === GENERATING_DASHBOARD_SYSTEM_TEXT ? (jsx(TextShimmer, { as: "span", children: text })) : (text) })) : isAssistant ? (jsx(AgentMessageContent, { text: text, onQuickReply: onQuickReply, suppressedQuickReplyKeys: suppressedQuickReplyKeys, quickReplyDisabled: quickReplyDisabled, isLastMessage: isLastMessage, scriptContinue: scriptContinue, onScriptContinue: onScriptContinue, renderMessageChart: renderMessageChart })) : (jsxs("div", { className: S.userColumn, children: [jsx("div", { className: S.text, children: text }), userCsvAttachment ? (jsx(UserCsvAttachmentBubble, { attachment: userCsvAttachment })) : null] })) }));
|
|
13
|
+
return (jsx("div", { className: cn(S.root, S[`role-${role}`]), children: isSystem ? (jsx("div", { className: S.text, children: text === GENERATING_DASHBOARD_SYSTEM_TEXT ? (jsx(TextShimmer, { as: "span", children: text })) : (text) })) : isAssistant ? (jsx(AgentMessageContent, { text: text, onQuickReply: onQuickReply, suppressedQuickReplyKeys: suppressedQuickReplyKeys, quickReplyDisabled: quickReplyDisabled, isLastMessage: isLastMessage, scriptContinue: scriptContinue, onScriptContinue: onScriptContinue, renderMessageChart: renderMessageChart })) : (jsxs("div", { className: S.userColumn, children: [jsx("div", { className: S.text, children: jsx(InteractiveContent, { text: text }) }), userCsvAttachment ? (jsx(UserCsvAttachmentBubble, { attachment: userCsvAttachment })) : null] })) }));
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
export { ChatMessage };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import styleInject from 'style-inject';
|
|
2
2
|
|
|
3
|
-
var css_248z = ".InteractiveContent_root__FHnlY strong{font-weight:600}";
|
|
3
|
+
var css_248z = ".InteractiveContent_root__FHnlY strong{font-weight:600}.InteractiveContent_root__FHnlY em{font-style:italic}.InteractiveContent_root__FHnlY a{color:var(--sb-green-600);text-decoration:underline;text-underline-offset:2px}.InteractiveContent_root__FHnlY a:hover{color:var(--sb-green-700)}.dark .InteractiveContent_root__FHnlY a{color:var(--sb-green-400)}.dark .InteractiveContent_root__FHnlY a:hover{color:var(--sb-green-300)}";
|
|
4
4
|
var S = {"root":"InteractiveContent_root__FHnlY"};
|
|
5
5
|
styleInject(css_248z);
|
|
6
6
|
|
|
@@ -9,6 +9,12 @@ declare const injectBold: (content: string) => {
|
|
|
9
9
|
index: number;
|
|
10
10
|
length: number;
|
|
11
11
|
};
|
|
12
|
+
/** `_italic_` (underscore) — avoids clashing with `* ` bullet markers. */
|
|
13
|
+
declare const injectItalic: (content: string) => {
|
|
14
|
+
elem: import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
index: number;
|
|
16
|
+
length: number;
|
|
17
|
+
};
|
|
12
18
|
declare const injectBullet: (content: string) => {
|
|
13
19
|
elem: import("react/jsx-runtime").JSX.Element;
|
|
14
20
|
index: number;
|
|
@@ -28,4 +34,4 @@ type Injector = (content: string) => {
|
|
|
28
34
|
declare const injectAnchor: Injector;
|
|
29
35
|
declare const applyFormatting: (text: string) => React.ReactNode[];
|
|
30
36
|
declare const applyFormattingInline: (text: string) => React.ReactNode[];
|
|
31
|
-
export { injectHeaders, injectAnchor, injectBold, injectBullet, injectNewlines, convertMarkdownTableToHTML, applyFormatting, applyFormattingInline, };
|
|
37
|
+
export { injectHeaders, injectAnchor, injectBold, injectItalic, injectBullet, injectNewlines, convertMarkdownTableToHTML, applyFormatting, applyFormattingInline, };
|
package/package.json
CHANGED
|
@@ -77,6 +77,23 @@ const injectBold = (content: string) => {
|
|
|
77
77
|
};
|
|
78
78
|
};
|
|
79
79
|
|
|
80
|
+
/** `_italic_` (underscore) — avoids clashing with `* ` bullet markers. */
|
|
81
|
+
const injectItalic = (content: string) => {
|
|
82
|
+
const matches = content.match(/(?<![A-Za-z0-9])_([^_\n]+?)_(?![A-Za-z0-9])/);
|
|
83
|
+
|
|
84
|
+
if (!matches || matches.index === undefined) return null;
|
|
85
|
+
|
|
86
|
+
if (isInsideHtmlListOrTable(content, matches.index)) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
elem: <em>{matches[1]}</em>,
|
|
92
|
+
index: matches.index,
|
|
93
|
+
length: matches[0].length,
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
|
|
80
97
|
const injectBullet = (content: string) => {
|
|
81
98
|
// Match bullet points: * or - followed by space
|
|
82
99
|
// Match at start of string/line or after whitespace/colon (for nested bullets)
|
|
@@ -281,6 +298,38 @@ const isAllowedHref = (href: string): boolean => {
|
|
|
281
298
|
return false;
|
|
282
299
|
};
|
|
283
300
|
|
|
301
|
+
const normalizeWwwToHttps = (raw: string): string => {
|
|
302
|
+
const t = raw.trim();
|
|
303
|
+
if (/^www\./i.test(t)) return `https://${t}`;
|
|
304
|
+
return t;
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
/** Strip ASCII closing punctuation often pasted after URLs. */
|
|
308
|
+
const stripTrailingUrlPunctuation = (s: string): string => {
|
|
309
|
+
let u = s;
|
|
310
|
+
while (u.length > 0 && /[.,;:!?)}\]]$/u.test(u)) {
|
|
311
|
+
u = u.slice(0, -1);
|
|
312
|
+
}
|
|
313
|
+
return u;
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
function linkTargetRelForHref(
|
|
317
|
+
href: string,
|
|
318
|
+
explicitTarget?: string,
|
|
319
|
+
): { target?: string; rel?: string } {
|
|
320
|
+
if (explicitTarget) {
|
|
321
|
+
const t = explicitTarget.trim();
|
|
322
|
+
if (t.toLowerCase() === '_blank') {
|
|
323
|
+
return { target: '_blank', rel: 'noopener noreferrer' };
|
|
324
|
+
}
|
|
325
|
+
return { target: t || undefined, rel: undefined };
|
|
326
|
+
}
|
|
327
|
+
if (/^https?:\/\//i.test(href) || /^mailto:/i.test(href)) {
|
|
328
|
+
return { target: '_blank', rel: 'noopener noreferrer' };
|
|
329
|
+
}
|
|
330
|
+
return { target: undefined, rel: undefined };
|
|
331
|
+
}
|
|
332
|
+
|
|
284
333
|
type Injector = (
|
|
285
334
|
content: string,
|
|
286
335
|
) => { elem: React.ReactNode; index: number; length: number } | null;
|
|
@@ -325,6 +374,100 @@ const runFormattingPipeline = (
|
|
|
325
374
|
return result;
|
|
326
375
|
};
|
|
327
376
|
|
|
377
|
+
const linkLabelInjectors: Injector[] = [
|
|
378
|
+
injectHTMLTags,
|
|
379
|
+
injectBold,
|
|
380
|
+
injectItalic,
|
|
381
|
+
injectNewlines,
|
|
382
|
+
];
|
|
383
|
+
|
|
384
|
+
const injectMarkdownLink: Injector = (content: string) => {
|
|
385
|
+
const matches = content.match(/\[([^\]]+)\]\(([^)]+)\)/);
|
|
386
|
+
|
|
387
|
+
if (!matches || matches.index === undefined) return null;
|
|
388
|
+
|
|
389
|
+
if (isInsideHtmlListOrTable(content, matches.index)) {
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const href = normalizeWwwToHttps(matches[2].trim());
|
|
394
|
+
if (!isAllowedHref(href)) return null;
|
|
395
|
+
|
|
396
|
+
const { target, rel } = linkTargetRelForHref(href, undefined);
|
|
397
|
+
const label = matches[1];
|
|
398
|
+
|
|
399
|
+
return {
|
|
400
|
+
elem: (
|
|
401
|
+
<a href={href} target={target} rel={rel}>
|
|
402
|
+
{runFormattingPipeline(label, linkLabelInjectors)}
|
|
403
|
+
</a>
|
|
404
|
+
),
|
|
405
|
+
index: matches.index,
|
|
406
|
+
length: matches[0].length,
|
|
407
|
+
};
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
const injectAutolinkUrl: Injector = (content: string) => {
|
|
411
|
+
type Cand = { index: number; length: number; href: string; display: string };
|
|
412
|
+
|
|
413
|
+
const candidates: Cand[] = [];
|
|
414
|
+
|
|
415
|
+
const reHttp = /https?:\/\/[^\s<>"']+/gi;
|
|
416
|
+
let hm: RegExpExecArray | null;
|
|
417
|
+
while ((hm = reHttp.exec(content)) !== null) {
|
|
418
|
+
const href = stripTrailingUrlPunctuation(hm[0]);
|
|
419
|
+
if (
|
|
420
|
+
href.length >= (href.startsWith('https://') ? 8 : 7) &&
|
|
421
|
+
isAllowedHref(href) &&
|
|
422
|
+
/^https?:\/\//i.test(href)
|
|
423
|
+
) {
|
|
424
|
+
candidates.push({
|
|
425
|
+
index: hm.index,
|
|
426
|
+
length: href.length,
|
|
427
|
+
href,
|
|
428
|
+
display: href,
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const reWww = /(^|[^A-Za-z0-9/])(www\.[^\s<>"']+)/gi;
|
|
434
|
+
while ((hm = reWww.exec(content)) !== null) {
|
|
435
|
+
const prefix = hm[1] ?? '';
|
|
436
|
+
const rawWww = hm[2];
|
|
437
|
+
const body = stripTrailingUrlPunctuation(rawWww);
|
|
438
|
+
const href = `https://${body}`;
|
|
439
|
+
if (body.length > 4 && isAllowedHref(href)) {
|
|
440
|
+
candidates.push({
|
|
441
|
+
index: hm.index + prefix.length,
|
|
442
|
+
length: body.length,
|
|
443
|
+
href,
|
|
444
|
+
display: body,
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (candidates.length === 0) return null;
|
|
450
|
+
|
|
451
|
+
candidates.sort((a, b) => a.index - b.index);
|
|
452
|
+
const c = candidates[0]!;
|
|
453
|
+
|
|
454
|
+
if (isInsideHtmlListOrTable(content, c.index)) {
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const { target, rel } = linkTargetRelForHref(c.href, undefined);
|
|
459
|
+
|
|
460
|
+
return {
|
|
461
|
+
elem: (
|
|
462
|
+
<a href={c.href} target={target} rel={rel}>
|
|
463
|
+
{c.display}
|
|
464
|
+
</a>
|
|
465
|
+
),
|
|
466
|
+
index: c.index,
|
|
467
|
+
length: c.length,
|
|
468
|
+
};
|
|
469
|
+
};
|
|
470
|
+
|
|
328
471
|
const injectAnchor: Injector = (content: string) => {
|
|
329
472
|
const regex = /<a\s+([^>]+)>([\s\S]*?)<\/a\s*>/i;
|
|
330
473
|
const matches = content.match(regex);
|
|
@@ -356,18 +499,12 @@ const injectAnchor: Injector = (content: string) => {
|
|
|
356
499
|
return null;
|
|
357
500
|
}
|
|
358
501
|
|
|
359
|
-
const
|
|
360
|
-
const target = opensNewTab ? '_blank' : targetRaw || undefined;
|
|
361
|
-
const rel = target === '_blank' ? 'noopener noreferrer' : undefined;
|
|
502
|
+
const { target, rel } = linkTargetRelForHref(href, targetRaw || undefined);
|
|
362
503
|
|
|
363
504
|
return {
|
|
364
505
|
elem: (
|
|
365
506
|
<a href={href} target={target} rel={rel}>
|
|
366
|
-
{runFormattingPipeline(inner,
|
|
367
|
-
injectHTMLTags,
|
|
368
|
-
injectBold,
|
|
369
|
-
injectNewlines,
|
|
370
|
-
])}
|
|
507
|
+
{runFormattingPipeline(inner, linkLabelInjectors)}
|
|
371
508
|
</a>
|
|
372
509
|
),
|
|
373
510
|
index: matches.index!,
|
|
@@ -379,17 +516,23 @@ const applyFormatting = (text: string): React.ReactNode[] =>
|
|
|
379
516
|
runFormattingPipeline(text, [
|
|
380
517
|
injectHeaders,
|
|
381
518
|
injectAnchor,
|
|
519
|
+
injectMarkdownLink,
|
|
382
520
|
injectHTMLTags,
|
|
383
521
|
injectBullet,
|
|
384
522
|
injectBold,
|
|
523
|
+
injectItalic,
|
|
524
|
+
injectAutolinkUrl,
|
|
385
525
|
injectNewlines,
|
|
386
526
|
]);
|
|
387
527
|
|
|
388
528
|
const applyFormattingInline = (text: string): React.ReactNode[] =>
|
|
389
529
|
runFormattingPipeline(text, [
|
|
390
530
|
injectAnchor,
|
|
531
|
+
injectMarkdownLink,
|
|
391
532
|
injectHTMLTags,
|
|
392
533
|
injectBold,
|
|
534
|
+
injectItalic,
|
|
535
|
+
injectAutolinkUrl,
|
|
393
536
|
injectNewlines,
|
|
394
537
|
]);
|
|
395
538
|
|
|
@@ -397,6 +540,7 @@ export {
|
|
|
397
540
|
injectHeaders,
|
|
398
541
|
injectAnchor,
|
|
399
542
|
injectBold,
|
|
543
|
+
injectItalic,
|
|
400
544
|
injectBullet,
|
|
401
545
|
injectNewlines,
|
|
402
546
|
convertMarkdownTableToHTML,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import cn from 'classnames';
|
|
2
2
|
|
|
3
|
+
import { InteractiveContent } from '#uilib/components/ui/InteractiveContent';
|
|
4
|
+
|
|
3
5
|
import { TextShimmer } from '../../TextShimmer';
|
|
4
6
|
import {
|
|
5
7
|
type ChatMessageProps,
|
|
@@ -48,7 +50,9 @@ export function ChatMessage({
|
|
|
48
50
|
/>
|
|
49
51
|
) : (
|
|
50
52
|
<div className={S.userColumn}>
|
|
51
|
-
<div className={S.text}>
|
|
53
|
+
<div className={S.text}>
|
|
54
|
+
<InteractiveContent text={text} />
|
|
55
|
+
</div>
|
|
52
56
|
{userCsvAttachment ? (
|
|
53
57
|
<UserCsvAttachmentBubble attachment={userCsvAttachment} />
|
|
54
58
|
) : null}
|
|
@@ -1,3 +1,20 @@
|
|
|
1
1
|
.root
|
|
2
2
|
strong
|
|
3
3
|
font-weight 600
|
|
4
|
+
|
|
5
|
+
em
|
|
6
|
+
font-style italic
|
|
7
|
+
|
|
8
|
+
a
|
|
9
|
+
color var(--sb-green-600)
|
|
10
|
+
text-decoration underline
|
|
11
|
+
text-underline-offset 2px
|
|
12
|
+
|
|
13
|
+
&:hover
|
|
14
|
+
color var(--sb-green-700)
|
|
15
|
+
|
|
16
|
+
:global(.dark) &
|
|
17
|
+
color var(--sb-green-400)
|
|
18
|
+
|
|
19
|
+
&:hover
|
|
20
|
+
color var(--sb-green-300)
|