@sybilion/uilib 1.2.21 → 1.2.23
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/icons/CsvIcon/CsvIcon.js +8 -0
- package/dist/esm/components/icons/CsvIcon/csv.svg.js +24 -0
- package/dist/esm/components/ui/Chat/ChatChrome/ChatChrome.js +1 -1
- 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/Chat/ChatMessage/ChatMessage.styl.js +1 -1
- package/dist/esm/components/ui/Chat/ChatMessage/UserCsvAttachmentBubble.js +1 -1
- package/dist/esm/components/ui/Chat/ChatPrompt/ChatPrompt.helpers.js +31 -0
- package/dist/esm/components/ui/Chat/ChatPrompt/ChatPrompt.js +11 -3
- package/dist/esm/components/ui/Chat/ChatPrompt/ChatPrompt.styl.js +2 -2
- package/dist/esm/components/ui/InteractiveContent/InteractiveContent.styl.js +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/types/src/components/ui/Chat/Chat.types.d.ts +2 -0
- package/dist/esm/types/src/components/ui/Chat/ChatMessage/AgentMessageContent.helpers.d.ts +7 -1
- package/dist/esm/types/src/components/ui/Chat/ChatPrompt/ChatPrompt.d.ts +1 -1
- package/dist/esm/types/src/components/ui/Chat/ChatPrompt/ChatPrompt.helpers.d.ts +6 -0
- package/dist/esm/types/src/components/ui/Chat/index.d.ts +1 -0
- package/package.json +1 -1
- package/src/components/icons/CsvIcon/CsvIcon.tsx +5 -0
- package/src/components/icons/CsvIcon/csv.svg +4 -0
- package/src/components/ui/Chat/Chat.types.ts +2 -0
- package/src/components/ui/Chat/ChatChrome/ChatChrome.tsx +1 -0
- package/src/components/ui/Chat/ChatMessage/AgentMessageContent.helpers.tsx +152 -8
- package/src/components/ui/Chat/ChatMessage/ChatMessage.styl +6 -11
- package/src/components/ui/Chat/ChatMessage/ChatMessage.tsx +5 -1
- package/src/components/ui/Chat/ChatMessage/UserCsvAttachmentBubble.tsx +2 -2
- package/src/components/ui/Chat/ChatPrompt/ChatPrompt.helpers.ts +43 -0
- package/src/components/ui/Chat/ChatPrompt/ChatPrompt.styl +36 -20
- package/src/components/ui/Chat/ChatPrompt/ChatPrompt.styl.d.ts +2 -1
- package/src/components/ui/Chat/ChatPrompt/ChatPrompt.tsx +28 -13
- package/src/components/ui/Chat/index.ts +1 -0
- package/src/components/ui/InteractiveContent/InteractiveContent.styl +17 -0
- package/dist/esm/components/ui/Chat/ChatMessage/icons/CsvIcon.js +0 -8
- package/src/components/ui/Chat/ChatMessage/icons/CsvIcon.tsx +0 -7
- /package/dist/esm/types/src/components/{ui/Chat/ChatMessage/icons → icons/CsvIcon}/CsvIcon.d.ts +0 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
4
|
+
var SvgCsv = function SvgCsv(props) {
|
|
5
|
+
return /*#__PURE__*/React.createElement("svg", _extends({
|
|
6
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
7
|
+
viewBox: "0 0 24 24"
|
|
8
|
+
}, props), /*#__PURE__*/React.createElement("path", {
|
|
9
|
+
fill: "#00a04e",
|
|
10
|
+
d: "M4.433 1.174c25.194-.106-5.93.182 14.987-.001.405.004.405.004.61.129.109.19.116.352.112.566-.01 3.297-.015 6.595-.029 9.893l.112-.004c.562-.017-.045.002 1.004-.024.197.038.266.11.385.27.062 2.563.035 5.128.03 7.692-.039.197-.117.27-.272.394-.415.043-.841.014-1.259 0q.017 1.22.03 2.44a.56.56 0 0 1-.18.317c-1.942.17-14.937.077-15.717.084-.46-.005-.46-.005-.61-.148a.57.57 0 0 1-.11-.39c.009-.767.014-1.535.027-2.303-.372.013-.744.019-1.116.028-.196-.038-.26-.117-.385-.27-.074-2.558-.034-5.13-.03-7.693.04-.2.109-.272.272-.393.415-.044.842-.014 1.26 0-.062-3.337.028-6.676-.032-10.013a.8.8 0 0 1 .114-.446c.255-.155.505-.13.797-.128m-.008.903v9.684h14.817V2.077Zm2.19 12.183c-.49.664-.597 1.45-.48 2.25.143.74.644 1.272 1.34 1.497.75.238 2.112-.06 2.325-1.356-.66-.237-.696-.242-.841-.24-.34.863-1 1.085-1.484.676-.693-.587-.45-1.823-.145-2.373.124-.08.559-.318 1.077-.139.22.15.358.334.473.575.297-.028.58-.074.871-.145a1.35 1.35 0 0 0-.445-.935c-.785-.619-1.99-.479-2.69.19m4.068-.15c-.435.461-.303 1.023-.228 1.24.175.395.463.552.854.706.438.217 1.085.13 1.395.547.067.145.076.4-.142.548-.343.114-.711.156-1.048.003-.202-.197-.303-.386-.408-.648l-.872.048c.014.416.138.806.431 1.114.512.406 1.123.425 1.748.387.366-.05.696-.181.969-.435.268-.37.323-.713.29-1.162-.06-.317-.238-.57-.484-.775-.376-.221-.821-.317-1.24-.425-.252-.067-.58-.19-.648-.301-.069-.112-.027-.257.048-.34.075-.082.363-.112.581-.114a.67.67 0 0 1 .581.211c.066.111.1.217.145.34.31.01.615.008.92-.05-.048-.39-.115-.724-.422-.991-.71-.491-1.818-.481-2.47.098m3.231-.365c.386 1.447.95 2.848 1.453 4.261h.968c.239-.644 1.51-4.082 1.501-4.261h-.92c-.356.876-.657 2.013-.968 2.808l-.145.049-.037-.182c-.255-.903-.578-1.787-.883-2.675zm-9.49 6.343v1.937h14.817v-1.937z",
|
|
11
|
+
style: {
|
|
12
|
+
strokeWidth: 0.0484209
|
|
13
|
+
}
|
|
14
|
+
}), /*#__PURE__*/React.createElement("path", {
|
|
15
|
+
fill: "#009649",
|
|
16
|
+
d: "M6.402 9.406c3.535-.028 7.565.04 11-.002.29.005.619.093.61.468-.036.436-.435.468-.63.467-3.155.025-9.165-.022-11.117.001-.29-.004-.594-.13-.612-.468.02-.426.433-.468.749-.466m-.112-2.71c3.536-.028 7.565.04 11-.002.29.004.62.092.612.468-.037.436-.436.468-.63.467-3.156.024-9.166-.022-11.118.001-.291-.004-.594-.13-.612-.468.02-.426.433-.468.749-.466m-.075-2.822c3.536-.027 7.565.04 11-.001.29.004.619.092.611.468-.036.436-.436.467-.63.466-3.155.025-9.165-.021-11.117.002-.292-.004-.595-.13-.612-.468.019-.427.433-.469.748-.467",
|
|
17
|
+
style: {
|
|
18
|
+
strokeWidth: 0.0484209
|
|
19
|
+
}
|
|
20
|
+
}));
|
|
21
|
+
};
|
|
22
|
+
var CsvSvg = SvgCsv;
|
|
23
|
+
|
|
24
|
+
export { CsvSvg as default };
|
|
@@ -42,7 +42,7 @@ function ChatChrome({ showResizeHandle, resizeHandle, onClose, isEmpty, renderPr
|
|
|
42
42
|
})
|
|
43
43
|
: null, isScriptComplete &&
|
|
44
44
|
onGenerateDashboard &&
|
|
45
|
-
!generatingDashboard ? (jsxs(Button, { type: "button", variant: "default", size: "lg", disabled: isLoading, onClick: onGenerateDashboardClick, children: [jsx(ChartLineIcon, {}), "Generate Dashboard"] })) : null] })), showInlinePresets && renderPresets('inline'), isLoading && isLastMessageFromUser && (jsx(TextShimmer, { duration: 1, spread: 5, className: S.loader, children: "Thinking..." }))] }) })), jsx("div", { className: cn(S.footer, footerClassName), children: jsx(Chat.Prompt, { onSubmit: onPromptSubmit, disabled: isLoading, prefillMessage: promptPrefill ?? undefined }) })] }) })] }));
|
|
45
|
+
!generatingDashboard ? (jsxs(Button, { type: "button", variant: "default", size: "lg", disabled: isLoading, onClick: onGenerateDashboardClick, children: [jsx(ChartLineIcon, {}), "Generate Dashboard"] })) : null] })), showInlinePresets && renderPresets('inline'), isLoading && isLastMessageFromUser && (jsx(TextShimmer, { duration: 1, spread: 5, className: S.loader, children: "Thinking..." }))] }) })), jsx("div", { className: cn(S.footer, footerClassName), children: jsx(Chat.Prompt, { onSubmit: onPromptSubmit, disabled: isLoading, prefillMessage: promptPrefill ?? undefined, showNotice: isEmpty }) })] }) })] }));
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
export { ChatChrome };
|
|
@@ -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 = ".ChatMessage_root__6rnsF{background:var(--bg-secondary);display:flex;flex-direction:column;gap:var(--p-1);padding:var(--p-6)}.ChatMessage_text__Y1XNR{color:var(--text-secondary);font-size:var(--text-sm);max-width:100%;-webkit-user-select:text;-moz-user-select:text;user-select:text;width:-moz-fit-content;width:fit-content}.ChatMessage_role-user__u4JPV{align-items:flex-end}.ChatMessage_role-user__u4JPV .ChatMessage_userColumn__cQM6-{align-items:flex-end;display:flex;flex-direction:column;gap:var(--p-2);max-width:100%}.ChatMessage_role-user__u4JPV .ChatMessage_text__Y1XNR{background-color:var(--sb-slate-100);border-radius:var(--p-4);border-bottom-right-radius:0;padding:var(--p-3) var(--p-4);white-space:pre-wrap}.dark .ChatMessage_role-user__u4JPV .ChatMessage_text__Y1XNR{background-color:var(--sb-gray-800)}.ChatMessage_role-user__u4JPV .ChatMessage_userCsvCard__D1M7y{align-items:center;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--
|
|
3
|
+
var css_248z = ".ChatMessage_root__6rnsF{background:var(--bg-secondary);display:flex;flex-direction:column;gap:var(--p-1);padding:var(--p-6)}.ChatMessage_text__Y1XNR{color:var(--text-secondary);font-size:var(--text-sm);max-width:100%;-webkit-user-select:text;-moz-user-select:text;user-select:text;width:-moz-fit-content;width:fit-content}.ChatMessage_role-user__u4JPV{align-items:flex-end}.ChatMessage_role-user__u4JPV .ChatMessage_userColumn__cQM6-{align-items:flex-end;display:flex;flex-direction:column;gap:var(--p-2);max-width:100%}.ChatMessage_role-user__u4JPV .ChatMessage_text__Y1XNR{background-color:var(--sb-slate-100);border-radius:var(--p-4);border-bottom-right-radius:0;padding:var(--p-3) var(--p-4);white-space:pre-wrap}.dark .ChatMessage_role-user__u4JPV .ChatMessage_text__Y1XNR{background-color:var(--sb-gray-800)}.ChatMessage_role-user__u4JPV .ChatMessage_userCsvCard__D1M7y{align-items:center;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--background);border:0;border-radius:var(--p-3);box-shadow:0 0 0 1px var(--border);cursor:pointer;display:flex;font:inherit;gap:var(--p-2);margin:0;max-width:100%;padding:var(--p-3);padding-right:var(--p-4);text-align:left;transition:background-color .15s;width:-moz-fit-content;width:fit-content}.ChatMessage_role-user__u4JPV .ChatMessage_userCsvCard__D1M7y:hover{background-color:var(--sb-gray-50)}.ChatMessage_role-user__u4JPV .ChatMessage_userCsvCard__D1M7y:focus-visible{outline:2px solid var(--ring);outline-offset:2px}.dark .ChatMessage_role-user__u4JPV .ChatMessage_userCsvCard__D1M7y{background-color:var(--sb-gray-800)}.dark .ChatMessage_role-user__u4JPV .ChatMessage_userCsvCard__D1M7y:hover{background-color:var(--sb-gray-900)}.ChatMessage_role-user__u4JPV .ChatMessage_userCsvCardIcon__0-KS6{align-items:center;display:flex;flex-shrink:0;height:32px;justify-content:center;width:32px}.ChatMessage_role-user__u4JPV .ChatMessage_userCsvCardContent__LoMGE{display:flex;flex:1;flex-direction:column;min-width:0}.ChatMessage_role-user__u4JPV .ChatMessage_userCsvCardTitle__9W76E{font-size:var(--text-sm);font-weight:500;line-height:1.4}.ChatMessage_role-user__u4JPV .ChatMessage_userCsvCardSubtitle__YZeHv{color:var(--muted-foreground);font-size:var(--text-xs);line-height:1.4}.ChatMessage_role-system__g13OP{align-items:center}.ChatMessage_role-system__g13OP .ChatMessage_text__Y1XNR{color:var(--muted-foreground);font-size:var(--text-xs);width:100%}.ChatMessage_role-assistant__wketE .ChatMessage_text__Y1XNR{width:100%}.ChatMessage_role-assistant__wketE h3{line-height:2.4}.ChatMessage_role-assistant__wketE h4{font-size:1.1em;font-weight:600;line-height:2.2}.ChatMessage_role-assistant__wketE .ChatMessage_bullet__6vAhq{display:inline-block;margin-left:4px;margin-right:6px}.ChatMessage_role-assistant__wketE .ChatMessage_bullet__6vAhq:before{color:var(--text-secondary);content:\"•\";display:inline-block}.ChatMessage_role-assistant__wketE .ChatMessage_scrollHorizontal__Rms9n{max-width:100%}.ChatMessage_role-assistant__wketE table{border:1px solid var(--border);border-collapse:collapse;border-radius:var(--p-2);border-spacing:0;margin:var(--p-4) 0;overflow:hidden}.ChatMessage_role-assistant__wketE table td,.ChatMessage_role-assistant__wketE table th{border:1px solid var(--border);min-width:100px;padding:var(--p-1)}.ChatMessage_role-assistant__wketE table th{text-align:left}.ChatMessage_role-assistant__wketE ol,.ChatMessage_role-assistant__wketE ul{padding-left:var(--p-4)}.ChatMessage_role-assistant__wketE ul{list-style-type:disc}.ChatMessage_role-assistant__wketE ol{list-style-type:decimal}.ChatMessage_role-assistant__wketE .ChatMessage_datasetAppLink__Pxy-T{align-items:center;border:1px dashed var(--sb-slate-300);border-radius:8px;color:var(--foreground);display:inline-flex;font-size:var(--text-xs);gap:6px;margin:1px;max-width:100%;padding:2px 6px 2px 4px;text-decoration:none;vertical-align:middle}.ChatMessage_role-assistant__wketE .ChatMessage_datasetAppLink__Pxy-T:hover{background-color:var(--sb-slate-50);border-color:var(--sb-slate-400);border-style:solid}.dark .ChatMessage_role-assistant__wketE .ChatMessage_datasetAppLink__Pxy-T{border-color:var(--sb-gray-600)}.dark .ChatMessage_role-assistant__wketE .ChatMessage_datasetAppLink__Pxy-T:hover{background-color:var(--sb-gray-900)}.ChatMessage_role-assistant__wketE .ChatMessage_datasetAppLinkLabel__PMU7e{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ChatMessage_role-assistant__wketE .ChatMessage_quickReplyWrap__1UFyD{display:inline-block;margin:var(--p-1) var(--p-1) var(--p-1) 0;vertical-align:middle}.ChatMessage_role-assistant__wketE .ChatMessage_downloadButtons__RygM-{display:flex;gap:var(--p-2);margin-top:var(--p-4)}.ChatMessage_role-assistant__wketE .ChatMessage_downloadCard__NsNRa{align-items:center;background-color:var(--background);border-radius:var(--p-3);box-shadow:0 0 0 1px var(--border);cursor:pointer;display:flex;gap:var(--p-4);margin-top:var(--p-3);padding:var(--p-3);padding-right:var(--p-4);transition:all .15s;width:-moz-fit-content;width:fit-content}.ChatMessage_role-assistant__wketE .ChatMessage_downloadCard__NsNRa:hover{background-color:var(--sb-gray-50);border-color:var(--border)}.dark .ChatMessage_role-assistant__wketE .ChatMessage_downloadCard__NsNRa{background-color:var(--sb-gray-900);border-color:var(--border)}.dark .ChatMessage_role-assistant__wketE .ChatMessage_downloadCard__NsNRa:hover{background-color:var(--sb-gray-800)}.ChatMessage_role-assistant__wketE .ChatMessage_downloadCardIcon__jkxDJ{align-items:center;border-radius:var(--p-2);display:flex;flex-shrink:0;height:32px;justify-content:center;width:32px}.ChatMessage_role-assistant__wketE .ChatMessage_downloadCardContent__PTPwz{display:flex;flex:1;flex-direction:column;min-width:0}.ChatMessage_role-assistant__wketE .ChatMessage_downloadCardTitle__K1wqr{font-size:var(--text-base);font-weight:600;line-height:1.4}.ChatMessage_role-assistant__wketE .ChatMessage_downloadCardSubtitle__fVeF2{color:var(--muted-foreground);font-size:var(--text-sm);line-height:1.4}";
|
|
4
4
|
var S = {"root":"ChatMessage_root__6rnsF","text":"ChatMessage_text__Y1XNR","role-user":"ChatMessage_role-user__u4JPV","userColumn":"ChatMessage_userColumn__cQM6-","userCsvCard":"ChatMessage_userCsvCard__D1M7y","userCsvCardIcon":"ChatMessage_userCsvCardIcon__0-KS6","userCsvCardContent":"ChatMessage_userCsvCardContent__LoMGE","userCsvCardTitle":"ChatMessage_userCsvCardTitle__9W76E","userCsvCardSubtitle":"ChatMessage_userCsvCardSubtitle__YZeHv","role-system":"ChatMessage_role-system__g13OP","role-assistant":"ChatMessage_role-assistant__wketE","bullet":"ChatMessage_bullet__6vAhq","scrollHorizontal":"ChatMessage_scrollHorizontal__Rms9n","datasetAppLink":"ChatMessage_datasetAppLink__Pxy-T","datasetAppLinkLabel":"ChatMessage_datasetAppLinkLabel__PMU7e","quickReplyWrap":"ChatMessage_quickReplyWrap__1UFyD","downloadButtons":"ChatMessage_downloadButtons__RygM-","downloadCard":"ChatMessage_downloadCard__NsNRa","downloadCardIcon":"ChatMessage_downloadCardIcon__jkxDJ","downloadCardContent":"ChatMessage_downloadCardContent__PTPwz","downloadCardTitle":"ChatMessage_downloadCardTitle__K1wqr","downloadCardSubtitle":"ChatMessage_downloadCardSubtitle__fVeF2"};
|
|
5
5
|
styleInject(css_248z);
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { downloadTextFile } from '../../../../utils/downloadTextFile.js';
|
|
3
|
-
import { CsvIcon } from '
|
|
3
|
+
import { CsvIcon } from '../../../icons/CsvIcon/CsvIcon.js';
|
|
4
4
|
import S from './ChatMessage.styl.js';
|
|
5
5
|
|
|
6
6
|
const CSV_DOWNLOAD_HINT = 'Download .CSV file';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Keep in sync with `ChatPrompt.styl` `INPUT_MAX_HEIGHT` / `min-height`. */
|
|
2
|
+
const PROMPT_INPUT_MAX_HEIGHT_PX = 200;
|
|
3
|
+
const PROMPT_INPUT_MIN_HEIGHT_PX = 40;
|
|
4
|
+
function chatPromptTextareaFloorPx(cs) {
|
|
5
|
+
const padY = (Number.parseFloat(cs.paddingTop) || 0) +
|
|
6
|
+
(Number.parseFloat(cs.paddingBottom) || 0);
|
|
7
|
+
const fs = Number.parseFloat(cs.fontSize) || 14;
|
|
8
|
+
const linePx = cs.lineHeight === 'normal'
|
|
9
|
+
? fs * 1.25
|
|
10
|
+
: Number.parseFloat(cs.lineHeight) || fs * 1.25;
|
|
11
|
+
return Math.max(PROMPT_INPUT_MIN_HEIGHT_PX, Math.ceil(linePx + padY));
|
|
12
|
+
}
|
|
13
|
+
/** Autosizing textarea inside flex shells: empty → floor only; typed → collapse measure + clamp. */
|
|
14
|
+
function syncChatPromptTextareaHeight(el, message) {
|
|
15
|
+
const floor = chatPromptTextareaFloorPx(getComputedStyle(el));
|
|
16
|
+
el.style.overflowY = 'hidden';
|
|
17
|
+
let contentHeight;
|
|
18
|
+
if (message === '') {
|
|
19
|
+
contentHeight = floor;
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
el.style.height = '0';
|
|
23
|
+
contentHeight = el.scrollHeight;
|
|
24
|
+
}
|
|
25
|
+
const h = Math.min(Math.max(contentHeight, floor), PROMPT_INPUT_MAX_HEIGHT_PX);
|
|
26
|
+
el.style.height = `${h}px`;
|
|
27
|
+
el.style.overflowY =
|
|
28
|
+
contentHeight > PROMPT_INPUT_MAX_HEIGHT_PX ? 'auto' : 'hidden';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { PROMPT_INPUT_MAX_HEIGHT_PX, PROMPT_INPUT_MIN_HEIGHT_PX, chatPromptTextareaFloorPx, syncChatPromptTextareaHeight };
|
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
2
|
import cn from 'classnames';
|
|
3
|
-
import { useState, useEffect } from 'react';
|
|
3
|
+
import { useState, useRef, useLayoutEffect, useEffect } from 'react';
|
|
4
4
|
import useEvent from '../../../../hooks/useEvent.js';
|
|
5
5
|
import { SendHorizontalIcon } from 'lucide-react';
|
|
6
6
|
import { Button } from '../../Button/Button.js';
|
|
7
7
|
import { Input } from '../../Input/Input.js';
|
|
8
|
+
import { syncChatPromptTextareaHeight } from './ChatPrompt.helpers.js';
|
|
8
9
|
import S from './ChatPrompt.styl.js';
|
|
9
10
|
|
|
10
|
-
function ChatPrompt({ onSubmit, placeholder, className, footer, prefillMessage, }) {
|
|
11
|
+
function ChatPrompt({ onSubmit, placeholder, className, footer, prefillMessage, showNotice = true, }) {
|
|
11
12
|
const [message, setMessage] = useState('');
|
|
13
|
+
const inputRef = useRef(null);
|
|
14
|
+
useLayoutEffect(() => {
|
|
15
|
+
const el = inputRef.current;
|
|
16
|
+
if (!el)
|
|
17
|
+
return;
|
|
18
|
+
syncChatPromptTextareaHeight(el, message);
|
|
19
|
+
}, [message]);
|
|
12
20
|
useEffect(() => {
|
|
13
21
|
if (prefillMessage != null && prefillMessage !== '') {
|
|
14
22
|
setMessage(prefillMessage);
|
|
@@ -32,7 +40,7 @@ function ChatPrompt({ onSubmit, placeholder, className, footer, prefillMessage,
|
|
|
32
40
|
}
|
|
33
41
|
},
|
|
34
42
|
});
|
|
35
|
-
return (jsxs("form", { onSubmit: handleSubmit, className: cn(S.root, className), children: [jsx(Input, { type: "textarea", value: message, onChange: e => setMessage(e.target.value), placeholder: placeholder || 'Type a message...', className: cn(S.input) }),
|
|
43
|
+
return (jsxs("form", { onSubmit: handleSubmit, className: cn(S.root, className), children: [jsxs("div", { className: S.composer, children: [showNotice ? (jsx("div", { className: S.notice, children: "Forecast Assistant can make mistakes." })) : null, jsx(Input, { ref: inputRef, type: "textarea", rows: 1, value: message, onChange: e => setMessage(e.target.value), placeholder: placeholder || 'Type a message...', className: cn(S.input) }), jsx("div", { className: S.submitColumn, children: jsx(Button, { type: "submit", size: "sm", disabled: !message.trim(), children: jsx(SendHorizontalIcon, { size: 16 }) }) })] }), footer] }));
|
|
36
44
|
}
|
|
37
45
|
|
|
38
46
|
export { ChatPrompt };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import styleInject from 'style-inject';
|
|
2
2
|
|
|
3
|
-
var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.ChatPrompt_root__5G5bq{align-items:
|
|
4
|
-
var S = {"root":"ChatPrompt_root__5G5bq","
|
|
3
|
+
var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.ChatPrompt_root__5G5bq{align-items:stretch;display:flex;flex-direction:column;gap:var(--p-2);padding:var(--p-6);padding-top:var(--p-5);position:relative}.ChatPrompt_composer__H3c3N{align-items:flex-end;display:flex;flex-direction:row;gap:var(--p-3);position:relative;width:100%}.ChatPrompt_notice__B-RyW{color:var(--muted-foreground);font-size:var(--text-xs);left:0;margin-bottom:var(--p-1);pointer-events:none;position:absolute;right:0;text-align:center;top:calc(var(--p-12)*-1)}@media (max-width:768px){.ChatPrompt_notice__B-RyW{font-size:10px}}.ChatPrompt_input__QPKBV{border:none;border-radius:0!important;flex:1;max-height:200px;min-height:40px;min-width:0;overflow-y:auto;padding:var(--p-2) 0 0!important;resize:none}.ChatPrompt_input__QPKBV,.ChatPrompt_input__QPKBV:focus{box-shadow:none!important}.ChatPrompt_submitColumn__0rY1R{align-items:center;display:flex;flex-direction:column;flex-shrink:0;justify-content:flex-end}.ChatPrompt_submitColumn__0rY1R>button:focus{box-shadow:0 0 0 2px var(--brand-color-500)!important}.ChatPrompt_submitColumn__0rY1R>button:first-child{border:none;position:relative;transition:all .2s}.ChatPrompt_submitColumn__0rY1R>button:first-child:focus{transform:scale(1.2)}.ChatPrompt_submitColumn__0rY1R>button:first-child:before{bottom:-100%;content:\"\";left:-100%;position:absolute;right:-100%;top:-100%}.ChatPrompt_submitColumn__0rY1R .ChatPrompt_attachButton__gi-qF{background-color:var(--page-color);box-shadow:0 0 20px var(--background)}";
|
|
4
|
+
var S = {"root":"ChatPrompt_root__5G5bq","composer":"ChatPrompt_composer__H3c3N","notice":"ChatPrompt_notice__B-RyW","input":"ChatPrompt_input__QPKBV","submitColumn":"ChatPrompt_submitColumn__0rY1R","attachButton":"ChatPrompt_attachButton__gi-qF"};
|
|
5
5
|
styleInject(css_248z);
|
|
6
6
|
|
|
7
7
|
export { S as default };
|
|
@@ -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
|
|
package/dist/esm/index.js
CHANGED
|
@@ -26,6 +26,7 @@ export { ChatMessage } from './components/ui/Chat/ChatMessage/ChatMessage.js';
|
|
|
26
26
|
export { ChatPrompt } from './components/ui/Chat/ChatPrompt/ChatPrompt.js';
|
|
27
27
|
export { ChatPresets } from './components/ui/Chat/ChatPresets/ChatPresets.js';
|
|
28
28
|
export { MessageRole } from './components/ui/Chat/Chat.types.js';
|
|
29
|
+
export { CsvIcon } from './components/icons/CsvIcon/CsvIcon.js';
|
|
29
30
|
export { Checkbox } from './components/ui/Checkbox/Checkbox.js';
|
|
30
31
|
export { Dialog } from './components/ui/Dialog/Dialog.js';
|
|
31
32
|
export { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger } from './components/ui/Drawer/Drawer.js';
|
|
@@ -66,6 +66,8 @@ export interface ChatPromptProps {
|
|
|
66
66
|
footer?: ReactNode;
|
|
67
67
|
/** Deep link `?prompt=` — one-shot composer pre-fill (see useChatPanelChromeModel). */
|
|
68
68
|
prefillMessage?: string | null;
|
|
69
|
+
/** Disclaimer above composer; default true. ChatChrome sets false when thread has messages. */
|
|
70
|
+
showNotice?: boolean;
|
|
69
71
|
}
|
|
70
72
|
export interface ChatMessageProps {
|
|
71
73
|
role: MessageRole;
|
|
@@ -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, };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { ChatPromptProps } from '../Chat.types';
|
|
2
|
-
export declare function ChatPrompt({ onSubmit, placeholder, className, footer, prefillMessage, }: ChatPromptProps): import("react/jsx-runtime").JSX.Element;
|
|
2
|
+
export declare function ChatPrompt({ onSubmit, placeholder, className, footer, prefillMessage, showNotice, }: ChatPromptProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Keep in sync with `ChatPrompt.styl` `INPUT_MAX_HEIGHT` / `min-height`. */
|
|
2
|
+
export declare const PROMPT_INPUT_MAX_HEIGHT_PX = 200;
|
|
3
|
+
export declare const PROMPT_INPUT_MIN_HEIGHT_PX = 40;
|
|
4
|
+
export declare function chatPromptTextareaFloorPx(cs: CSSStyleDeclaration): number;
|
|
5
|
+
/** Autosizing textarea inside flex shells: empty → floor only; typed → collapse measure + clamp. */
|
|
6
|
+
export declare function syncChatPromptTextareaHeight(el: HTMLTextAreaElement, message: string): void;
|
|
@@ -11,3 +11,4 @@ export { ChatPrompt } from './ChatPrompt';
|
|
|
11
11
|
export { ChatPresets } from './ChatPresets';
|
|
12
12
|
export type { Chat as ChatType, ChatSendMessagePayload, ChatProps, ChatPreset as ChatPresetType, Message, UserCsvAttachment, } from './Chat.types';
|
|
13
13
|
export { MessageRole } from './Chat.types';
|
|
14
|
+
export { CsvIcon } from '../../icons/CsvIcon/CsvIcon';
|
package/package.json
CHANGED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
2
|
+
<path fill="#00a04e" d="M4.433 1.174c25.194-.106-5.93.182 14.987-.001.405.004.405.004.61.129.109.19.116.352.112.566-.01 3.297-.015 6.595-.029 9.893l.112-.004c.562-.017-.045.002 1.004-.024.197.038.266.11.385.27.062 2.563.035 5.128.03 7.692-.039.197-.117.27-.272.394-.415.043-.841.014-1.259 0q.017 1.22.03 2.44a.56.56 0 0 1-.18.317c-1.942.17-14.937.077-15.717.084-.46-.005-.46-.005-.61-.148a.57.57 0 0 1-.11-.39c.009-.767.014-1.535.027-2.303-.372.013-.744.019-1.116.028-.196-.038-.26-.117-.385-.27-.074-2.558-.034-5.13-.03-7.693.04-.2.109-.272.272-.393.415-.044.842-.014 1.26 0-.062-3.337.028-6.676-.032-10.013a.8.8 0 0 1 .114-.446c.255-.155.505-.13.797-.128m-.008.903v9.684h14.817V2.077Zm2.19 12.183c-.49.664-.597 1.45-.48 2.25.143.74.644 1.272 1.34 1.497.75.238 2.112-.06 2.325-1.356-.66-.237-.696-.242-.841-.24-.34.863-1 1.085-1.484.676-.693-.587-.45-1.823-.145-2.373.124-.08.559-.318 1.077-.139.22.15.358.334.473.575.297-.028.58-.074.871-.145a1.35 1.35 0 0 0-.445-.935c-.785-.619-1.99-.479-2.69.19m4.068-.15c-.435.461-.303 1.023-.228 1.24.175.395.463.552.854.706.438.217 1.085.13 1.395.547.067.145.076.4-.142.548-.343.114-.711.156-1.048.003-.202-.197-.303-.386-.408-.648l-.872.048c.014.416.138.806.431 1.114.512.406 1.123.425 1.748.387.366-.05.696-.181.969-.435.268-.37.323-.713.29-1.162-.06-.317-.238-.57-.484-.775-.376-.221-.821-.317-1.24-.425-.252-.067-.58-.19-.648-.301-.069-.112-.027-.257.048-.34.075-.082.363-.112.581-.114a.67.67 0 0 1 .581.211c.066.111.1.217.145.34.31.01.615.008.92-.05-.048-.39-.115-.724-.422-.991-.71-.491-1.818-.481-2.47.098m3.231-.365c.386 1.447.95 2.848 1.453 4.261h.968c.239-.644 1.51-4.082 1.501-4.261h-.92c-.356.876-.657 2.013-.968 2.808l-.145.049-.037-.182c-.255-.903-.578-1.787-.883-2.675zm-9.49 6.343v1.937h14.817v-1.937z" style="stroke-width:.0484209" />
|
|
3
|
+
<path fill="#009649" d="M6.402 9.406c3.535-.028 7.565.04 11-.002.29.005.619.093.61.468-.036.436-.435.468-.63.467-3.155.025-9.165-.022-11.117.001-.29-.004-.594-.13-.612-.468.02-.426.433-.468.749-.466m-.112-2.71c3.536-.028 7.565.04 11-.002.29.004.62.092.612.468-.037.436-.436.468-.63.467-3.156.024-9.166-.022-11.118.001-.291-.004-.594-.13-.612-.468.02-.426.433-.468.749-.466m-.075-2.822c3.536-.027 7.565.04 11-.001.29.004.619.092.611.468-.036.436-.436.467-.63.466-3.155.025-9.165-.021-11.117.002-.292-.004-.595-.13-.612-.468.019-.427.433-.469.748-.467" style="stroke-width:.0484209" />
|
|
4
|
+
</svg>
|
|
@@ -76,6 +76,8 @@ export interface ChatPromptProps {
|
|
|
76
76
|
footer?: ReactNode;
|
|
77
77
|
/** Deep link `?prompt=` — one-shot composer pre-fill (see useChatPanelChromeModel). */
|
|
78
78
|
prefillMessage?: string | null;
|
|
79
|
+
/** Disclaimer above composer; default true. ChatChrome sets false when thread has messages. */
|
|
80
|
+
showNotice?: boolean;
|
|
79
81
|
}
|
|
80
82
|
|
|
81
83
|
export interface ChatMessageProps {
|
|
@@ -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,
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
|
|
9
9
|
.text
|
|
10
10
|
max-width 100%
|
|
11
|
-
// padding 0 var(--p-4)
|
|
12
11
|
font-size var(--text-sm)
|
|
13
12
|
color var(--text-secondary)
|
|
14
13
|
width fit-content
|
|
@@ -43,19 +42,17 @@
|
|
|
43
42
|
font inherit
|
|
44
43
|
display flex
|
|
45
44
|
align-items center
|
|
46
|
-
gap var(--p-
|
|
45
|
+
gap var(--p-2)
|
|
47
46
|
padding var(--p-3)
|
|
48
47
|
padding-right var(--p-4)
|
|
49
|
-
background-color var(--
|
|
48
|
+
background-color var(--background)
|
|
50
49
|
box-shadow 0 0 0 1px var(--border)
|
|
51
|
-
border-radius var(--p-
|
|
52
|
-
border-bottom-right-radius 0
|
|
50
|
+
border-radius var(--p-3)
|
|
53
51
|
width fit-content
|
|
54
52
|
max-width 100%
|
|
55
53
|
text-align left
|
|
56
54
|
cursor pointer
|
|
57
55
|
transition background-color 150ms
|
|
58
|
-
color var(--sb-green-600)
|
|
59
56
|
|
|
60
57
|
&:hover
|
|
61
58
|
background-color var(--sb-gray-50)
|
|
@@ -66,7 +63,6 @@
|
|
|
66
63
|
|
|
67
64
|
:global(.dark) &
|
|
68
65
|
background-color var(--sb-gray-800)
|
|
69
|
-
color var(--sb-green-400)
|
|
70
66
|
|
|
71
67
|
&:hover
|
|
72
68
|
background-color var(--sb-gray-900)
|
|
@@ -86,13 +82,12 @@
|
|
|
86
82
|
min-width 0
|
|
87
83
|
|
|
88
84
|
.userCsvCardTitle
|
|
89
|
-
font-size var(--text-
|
|
90
|
-
font-weight
|
|
85
|
+
font-size var(--text-sm)
|
|
86
|
+
font-weight 500
|
|
91
87
|
line-height 1.4
|
|
92
|
-
color var(--text-secondary)
|
|
93
88
|
|
|
94
89
|
.userCsvCardSubtitle
|
|
95
|
-
font-size var(--text-
|
|
90
|
+
font-size var(--text-xs)
|
|
96
91
|
color var(--muted-foreground)
|
|
97
92
|
line-height 1.4
|
|
98
93
|
|
|
@@ -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,7 +1,7 @@
|
|
|
1
|
-
import type { UserCsvAttachment } from '../Chat.types';
|
|
2
1
|
import { downloadTextFile } from '#uilib/utils/downloadTextFile';
|
|
3
2
|
|
|
4
|
-
import { CsvIcon } from '
|
|
3
|
+
import { CsvIcon } from '../../../icons/CsvIcon/CsvIcon';
|
|
4
|
+
import type { UserCsvAttachment } from '../Chat.types';
|
|
5
5
|
import S from './ChatMessage.styl';
|
|
6
6
|
|
|
7
7
|
const CSV_DOWNLOAD_HINT = 'Download .CSV file';
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** Keep in sync with `ChatPrompt.styl` `INPUT_MAX_HEIGHT` / `min-height`. */
|
|
2
|
+
export const PROMPT_INPUT_MAX_HEIGHT_PX = 200;
|
|
3
|
+
export const PROMPT_INPUT_MIN_HEIGHT_PX = 40;
|
|
4
|
+
|
|
5
|
+
export function chatPromptTextareaFloorPx(cs: CSSStyleDeclaration): number {
|
|
6
|
+
const padY =
|
|
7
|
+
(Number.parseFloat(cs.paddingTop) || 0) +
|
|
8
|
+
(Number.parseFloat(cs.paddingBottom) || 0);
|
|
9
|
+
const fs = Number.parseFloat(cs.fontSize) || 14;
|
|
10
|
+
const linePx =
|
|
11
|
+
cs.lineHeight === 'normal'
|
|
12
|
+
? fs * 1.25
|
|
13
|
+
: Number.parseFloat(cs.lineHeight) || fs * 1.25;
|
|
14
|
+
|
|
15
|
+
return Math.max(PROMPT_INPUT_MIN_HEIGHT_PX, Math.ceil(linePx + padY));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Autosizing textarea inside flex shells: empty → floor only; typed → collapse measure + clamp. */
|
|
19
|
+
export function syncChatPromptTextareaHeight(
|
|
20
|
+
el: HTMLTextAreaElement,
|
|
21
|
+
message: string,
|
|
22
|
+
): void {
|
|
23
|
+
const floor = chatPromptTextareaFloorPx(getComputedStyle(el));
|
|
24
|
+
|
|
25
|
+
el.style.overflowY = 'hidden';
|
|
26
|
+
|
|
27
|
+
let contentHeight: number;
|
|
28
|
+
if (message === '') {
|
|
29
|
+
contentHeight = floor;
|
|
30
|
+
} else {
|
|
31
|
+
el.style.height = '0';
|
|
32
|
+
contentHeight = el.scrollHeight;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const h = Math.min(
|
|
36
|
+
Math.max(contentHeight, floor),
|
|
37
|
+
PROMPT_INPUT_MAX_HEIGHT_PX,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
el.style.height = `${h}px`;
|
|
41
|
+
el.style.overflowY =
|
|
42
|
+
contentHeight > PROMPT_INPUT_MAX_HEIGHT_PX ? 'auto' : 'hidden';
|
|
43
|
+
}
|
|
@@ -1,33 +1,60 @@
|
|
|
1
1
|
@import 'lib/theme.styl';
|
|
2
2
|
|
|
3
|
+
INPUT_MAX_HEIGHT = 200px
|
|
4
|
+
|
|
3
5
|
.root
|
|
4
6
|
position relative
|
|
5
7
|
display flex
|
|
6
8
|
flex-direction column
|
|
7
9
|
gap var(--p-2)
|
|
8
|
-
align-items
|
|
10
|
+
align-items stretch
|
|
9
11
|
padding var(--p-6)
|
|
12
|
+
padding-top var(--p-5)
|
|
13
|
+
|
|
14
|
+
.composer
|
|
15
|
+
position relative
|
|
16
|
+
display flex
|
|
17
|
+
flex-direction row
|
|
18
|
+
align-items flex-end
|
|
19
|
+
gap var(--p-3)
|
|
20
|
+
width 100%
|
|
21
|
+
|
|
22
|
+
.notice
|
|
23
|
+
position absolute
|
|
24
|
+
top calc(-1 * var(--p-12))
|
|
25
|
+
left 0
|
|
26
|
+
right 0
|
|
27
|
+
margin-bottom var(--p-1)
|
|
28
|
+
|
|
29
|
+
font-size var(--text-xs)
|
|
30
|
+
text-align center
|
|
31
|
+
color var(--muted-foreground)
|
|
32
|
+
pointer-events none
|
|
33
|
+
|
|
34
|
+
@media (max-width MOBILE)
|
|
35
|
+
font-size 10px
|
|
10
36
|
|
|
11
37
|
.input
|
|
12
38
|
flex 1
|
|
13
|
-
min-
|
|
39
|
+
min-width 0
|
|
40
|
+
min-height 40px
|
|
41
|
+
max-height INPUT_MAX_HEIGHT
|
|
14
42
|
resize none
|
|
43
|
+
overflow-y auto
|
|
15
44
|
border none
|
|
16
|
-
padding 0 !important
|
|
45
|
+
padding var(--p-2) 0 0 !important
|
|
17
46
|
border-radius 0 !important
|
|
18
47
|
box-shadow none !important
|
|
19
48
|
|
|
20
49
|
&:focus
|
|
21
50
|
box-shadow none !important
|
|
22
51
|
|
|
23
|
-
.
|
|
52
|
+
.submitColumn
|
|
24
53
|
display flex
|
|
54
|
+
flex-direction column
|
|
55
|
+
flex-shrink 0
|
|
25
56
|
align-items center
|
|
26
|
-
|
|
27
|
-
justify-content space-between
|
|
28
|
-
gap var(--p-2)
|
|
29
|
-
|
|
30
|
-
width 100%
|
|
57
|
+
justify-content flex-end
|
|
31
58
|
|
|
32
59
|
& > button:focus
|
|
33
60
|
box-shadow 0 0 0 2px var(--brand-color-500) !important
|
|
@@ -52,14 +79,3 @@
|
|
|
52
79
|
.attachButton
|
|
53
80
|
background-color var(--page-color)
|
|
54
81
|
box-shadow 0 0 20px var(--background)
|
|
55
|
-
|
|
56
|
-
.notice
|
|
57
|
-
font-size var(--text-xs)
|
|
58
|
-
text-align center
|
|
59
|
-
color var(--muted-foreground)
|
|
60
|
-
width 100%
|
|
61
|
-
// padding 0 var(--p-9)
|
|
62
|
-
// text-align center
|
|
63
|
-
|
|
64
|
-
@media (max-width MOBILE)
|
|
65
|
-
font-size 10px
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// This file is automatically generated.
|
|
2
2
|
// Please do not change this file!
|
|
3
3
|
interface CssExports {
|
|
4
|
-
'actions': string;
|
|
5
4
|
'attachButton': string;
|
|
5
|
+
'composer': string;
|
|
6
6
|
'input': string;
|
|
7
7
|
'notice': string;
|
|
8
8
|
'root': string;
|
|
9
|
+
'submitColumn': string;
|
|
9
10
|
}
|
|
10
11
|
export const cssExports: CssExports;
|
|
11
12
|
export default cssExports;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import cn from 'classnames';
|
|
2
|
-
import { FormEvent, useEffect, useState } from 'react';
|
|
2
|
+
import { FormEvent, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
3
3
|
|
|
4
4
|
import useEvent from '#uilib/hooks/useEvent';
|
|
5
5
|
import { SendHorizontalIcon } from 'lucide-react';
|
|
@@ -7,6 +7,7 @@ import { SendHorizontalIcon } from 'lucide-react';
|
|
|
7
7
|
import { Button } from '../../Button';
|
|
8
8
|
import { Input } from '../../Input';
|
|
9
9
|
import type { ChatPromptProps } from '../Chat.types';
|
|
10
|
+
import { syncChatPromptTextareaHeight } from './ChatPrompt.helpers';
|
|
10
11
|
import S from './ChatPrompt.styl';
|
|
11
12
|
|
|
12
13
|
export function ChatPrompt({
|
|
@@ -15,8 +16,16 @@ export function ChatPrompt({
|
|
|
15
16
|
className,
|
|
16
17
|
footer,
|
|
17
18
|
prefillMessage,
|
|
19
|
+
showNotice = true,
|
|
18
20
|
}: ChatPromptProps) {
|
|
19
21
|
const [message, setMessage] = useState('');
|
|
22
|
+
const inputRef = useRef<HTMLTextAreaElement>(null);
|
|
23
|
+
|
|
24
|
+
useLayoutEffect(() => {
|
|
25
|
+
const el = inputRef.current;
|
|
26
|
+
if (!el) return;
|
|
27
|
+
syncChatPromptTextareaHeight(el, message);
|
|
28
|
+
}, [message]);
|
|
20
29
|
|
|
21
30
|
useEffect(() => {
|
|
22
31
|
if (prefillMessage != null && prefillMessage !== '') {
|
|
@@ -47,20 +56,26 @@ export function ChatPrompt({
|
|
|
47
56
|
|
|
48
57
|
return (
|
|
49
58
|
<form onSubmit={handleSubmit} className={cn(S.root, className)}>
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
placeholder={placeholder || 'Type a message...'}
|
|
55
|
-
className={cn(S.input)}
|
|
56
|
-
/>
|
|
59
|
+
<div className={S.composer}>
|
|
60
|
+
{showNotice ? (
|
|
61
|
+
<div className={S.notice}>Forecast Assistant can make mistakes.</div>
|
|
62
|
+
) : null}
|
|
57
63
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
64
|
+
<Input
|
|
65
|
+
ref={inputRef}
|
|
66
|
+
type="textarea"
|
|
67
|
+
rows={1}
|
|
68
|
+
value={message}
|
|
69
|
+
onChange={e => setMessage(e.target.value)}
|
|
70
|
+
placeholder={placeholder || 'Type a message...'}
|
|
71
|
+
className={cn(S.input)}
|
|
72
|
+
/>
|
|
62
73
|
|
|
63
|
-
<div className={S.
|
|
74
|
+
<div className={S.submitColumn}>
|
|
75
|
+
<Button type="submit" size="sm" disabled={!message.trim()}>
|
|
76
|
+
<SendHorizontalIcon size={16} />
|
|
77
|
+
</Button>
|
|
78
|
+
</div>
|
|
64
79
|
|
|
65
80
|
{/* <Button
|
|
66
81
|
variant="outline"
|
|
@@ -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)
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { jsx } from 'react/jsx-runtime';
|
|
2
|
-
import { FileSpreadsheet } from 'lucide-react';
|
|
3
|
-
|
|
4
|
-
function CsvIcon({ size = 32 }) {
|
|
5
|
-
return (jsx(FileSpreadsheet, { size: size, "aria-hidden": true, strokeWidth: 1.75, color: "currentColor" }));
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export { CsvIcon };
|
/package/dist/esm/types/src/components/{ui/Chat/ChatMessage/icons → icons/CsvIcon}/CsvIcon.d.ts
RENAMED
|
File without changes
|