@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.
Files changed (35) hide show
  1. package/dist/esm/components/icons/CsvIcon/CsvIcon.js +8 -0
  2. package/dist/esm/components/icons/CsvIcon/csv.svg.js +24 -0
  3. package/dist/esm/components/ui/Chat/ChatChrome/ChatChrome.js +1 -1
  4. package/dist/esm/components/ui/Chat/ChatMessage/AgentMessageContent.helpers.js +120 -9
  5. package/dist/esm/components/ui/Chat/ChatMessage/ChatMessage.js +2 -1
  6. package/dist/esm/components/ui/Chat/ChatMessage/ChatMessage.styl.js +1 -1
  7. package/dist/esm/components/ui/Chat/ChatMessage/UserCsvAttachmentBubble.js +1 -1
  8. package/dist/esm/components/ui/Chat/ChatPrompt/ChatPrompt.helpers.js +31 -0
  9. package/dist/esm/components/ui/Chat/ChatPrompt/ChatPrompt.js +11 -3
  10. package/dist/esm/components/ui/Chat/ChatPrompt/ChatPrompt.styl.js +2 -2
  11. package/dist/esm/components/ui/InteractiveContent/InteractiveContent.styl.js +1 -1
  12. package/dist/esm/index.js +1 -0
  13. package/dist/esm/types/src/components/ui/Chat/Chat.types.d.ts +2 -0
  14. package/dist/esm/types/src/components/ui/Chat/ChatMessage/AgentMessageContent.helpers.d.ts +7 -1
  15. package/dist/esm/types/src/components/ui/Chat/ChatPrompt/ChatPrompt.d.ts +1 -1
  16. package/dist/esm/types/src/components/ui/Chat/ChatPrompt/ChatPrompt.helpers.d.ts +6 -0
  17. package/dist/esm/types/src/components/ui/Chat/index.d.ts +1 -0
  18. package/package.json +1 -1
  19. package/src/components/icons/CsvIcon/CsvIcon.tsx +5 -0
  20. package/src/components/icons/CsvIcon/csv.svg +4 -0
  21. package/src/components/ui/Chat/Chat.types.ts +2 -0
  22. package/src/components/ui/Chat/ChatChrome/ChatChrome.tsx +1 -0
  23. package/src/components/ui/Chat/ChatMessage/AgentMessageContent.helpers.tsx +152 -8
  24. package/src/components/ui/Chat/ChatMessage/ChatMessage.styl +6 -11
  25. package/src/components/ui/Chat/ChatMessage/ChatMessage.tsx +5 -1
  26. package/src/components/ui/Chat/ChatMessage/UserCsvAttachmentBubble.tsx +2 -2
  27. package/src/components/ui/Chat/ChatPrompt/ChatPrompt.helpers.ts +43 -0
  28. package/src/components/ui/Chat/ChatPrompt/ChatPrompt.styl +36 -20
  29. package/src/components/ui/Chat/ChatPrompt/ChatPrompt.styl.d.ts +2 -1
  30. package/src/components/ui/Chat/ChatPrompt/ChatPrompt.tsx +28 -13
  31. package/src/components/ui/Chat/index.ts +1 -0
  32. package/src/components/ui/InteractiveContent/InteractiveContent.styl +17 -0
  33. package/dist/esm/components/ui/Chat/ChatMessage/icons/CsvIcon.js +0 -8
  34. package/src/components/ui/Chat/ChatMessage/icons/CsvIcon.tsx +0 -7
  35. /package/dist/esm/types/src/components/{ui/Chat/ChatMessage/icons → icons/CsvIcon}/CsvIcon.d.ts +0 -0
@@ -0,0 +1,8 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import CsvSvg from './csv.svg.js';
3
+
4
+ function CsvIcon({ size = 32 }) {
5
+ return jsx(CsvSvg, { width: size, height: size, "aria-hidden": true });
6
+ }
7
+
8
+ export { CsvIcon };
@@ -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 opensNewTab = targetRaw.toLowerCase() === '_blank';
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(--sb-slate-100);border:0;border-radius:var(--p-4);border-bottom-right-radius:0;box-shadow:0 0 0 1px var(--border);color:var(--sb-green-600);cursor:pointer;display:flex;font:inherit;gap:var(--p-4);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);color:var(--sb-green-400)}.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{color:var(--text-secondary);font-size:var(--text-base);font-weight:600;line-height:1.4}.ChatMessage_role-user__u4JPV .ChatMessage_userCsvCardSubtitle__YZeHv{color:var(--muted-foreground);font-size:var(--text-sm);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}";
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 './icons/CsvIcon.js';
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) }), jsxs("div", { className: S.actions, children: [jsx(Button, { type: "submit", size: "sm", disabled: !message.trim(), children: jsx(SendHorizontalIcon, { size: 16 }) }), jsx("div", { className: S.notice, children: "Forecast Assistant can make mistakes." })] }), footer] }));
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:center;display:flex;flex-direction:column;gap:var(--p-2);padding:var(--p-6);position:relative}.ChatPrompt_input__QPKBV{border:none;border-radius:0!important;flex:1;min-height:70px;padding:0!important;resize:none}.ChatPrompt_input__QPKBV,.ChatPrompt_input__QPKBV:focus{box-shadow:none!important}.ChatPrompt_actions__pUtB-{align-items:center;display:flex;flex-direction:row-reverse;gap:var(--p-2);justify-content:space-between;width:100%}.ChatPrompt_actions__pUtB->button:focus{box-shadow:0 0 0 2px var(--brand-color-500)!important}.ChatPrompt_actions__pUtB->button:first-child{border:none;position:relative;transition:all .2s}.ChatPrompt_actions__pUtB->button:first-child:focus{transform:scale(1.2)}.ChatPrompt_actions__pUtB->button:first-child:before{bottom:-100%;content:\"\";left:-100%;position:absolute;right:-100%;top:-100%}.ChatPrompt_actions__pUtB- .ChatPrompt_attachButton__gi-qF{background-color:var(--page-color);box-shadow:0 0 20px var(--background)}.ChatPrompt_notice__B-RyW{color:var(--muted-foreground);font-size:var(--text-xs);text-align:center;width:100%}@media (max-width:768px){.ChatPrompt_notice__B-RyW{font-size:10px}}";
4
- var S = {"root":"ChatPrompt_root__5G5bq","input":"ChatPrompt_input__QPKBV","actions":"ChatPrompt_actions__pUtB-","attachButton":"ChatPrompt_attachButton__gi-qF","notice":"ChatPrompt_notice__B-RyW"};
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sybilion/uilib",
3
- "version": "1.2.21",
3
+ "version": "1.2.23",
4
4
  "description": "Sybilion Design System — React UI components (Webpack + Stylus)",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -0,0 +1,5 @@
1
+ import CsvSvg from './csv.svg';
2
+
3
+ export function CsvIcon({ size = 32 }: { size?: number }) {
4
+ return <CsvSvg width={size} height={size} aria-hidden />;
5
+ }
@@ -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 {
@@ -199,6 +199,7 @@ export function ChatChrome({
199
199
  onSubmit={onPromptSubmit}
200
200
  disabled={isLoading}
201
201
  prefillMessage={promptPrefill ?? undefined}
202
+ showNotice={isEmpty}
202
203
  />
203
204
  </div>
204
205
  </Chat>
@@ -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 opensNewTab = targetRaw.toLowerCase() === '_blank';
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-4)
45
+ gap var(--p-2)
47
46
  padding var(--p-3)
48
47
  padding-right var(--p-4)
49
- background-color var(--sb-slate-100)
48
+ background-color var(--background)
50
49
  box-shadow 0 0 0 1px var(--border)
51
- border-radius var(--p-4)
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-base)
90
- font-weight 600
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-sm)
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}>{text}</div>
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 './icons/CsvIcon';
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 center
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-height 70px
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
- .actions
52
+ .submitColumn
24
53
  display flex
54
+ flex-direction column
55
+ flex-shrink 0
25
56
  align-items center
26
- flex-direction row-reverse
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
- <Input
51
- type="textarea"
52
- value={message}
53
- onChange={e => setMessage(e.target.value)}
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
- <div className={S.actions}>
59
- <Button type="submit" size="sm" disabled={!message.trim()}>
60
- <SendHorizontalIcon size={16} />
61
- </Button>
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.notice}>Forecast Assistant can make mistakes.</div>
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"
@@ -24,3 +24,4 @@ export type {
24
24
  UserCsvAttachment,
25
25
  } from './Chat.types';
26
26
  export { MessageRole } from './Chat.types';
27
+ export { CsvIcon } from '../../icons/CsvIcon/CsvIcon';
@@ -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 };
@@ -1,7 +0,0 @@
1
- import { FileSpreadsheet } from 'lucide-react';
2
-
3
- export function CsvIcon({ size = 32 }: { size?: number }) {
4
- return (
5
- <FileSpreadsheet size={size} aria-hidden strokeWidth={1.75} color="currentColor" />
6
- );
7
- }