@sybilion/uilib 1.3.78 → 1.3.80

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 (51) hide show
  1. package/dist/esm/components/ui/Chat/Chat.js +2 -2
  2. package/dist/esm/components/ui/Chat/Chat.styl.js +1 -1
  3. package/dist/esm/components/ui/Chat/ChatChrome/ChatChrome.js +10 -6
  4. package/dist/esm/components/ui/Chat/ChatChrome/ChatChrome.styl.js +2 -2
  5. package/dist/esm/components/ui/Chat/ChatEmptyState/ChatEmptyState.styl.js +1 -1
  6. package/dist/esm/components/ui/Chat/ChatMessage/ChatMessage.js +2 -2
  7. package/dist/esm/components/ui/Chat/ChatPresets/ChatPresets.styl.js +1 -1
  8. package/dist/esm/components/ui/Chat/ChatSheet/ChatSelector.js +2 -2
  9. package/dist/esm/components/ui/Chat/ChatSheet/ChatSheet.js +2 -1
  10. package/dist/esm/components/ui/Chat/ChatSheet/useChatPanelChromeModel.js +6 -5
  11. package/dist/esm/components/ui/Chat/buildChatSendMessagePayload.js +3 -1
  12. package/dist/esm/components/ui/FileChip/FileChip.js +4 -0
  13. package/dist/esm/components/widgets/DriversComparisonChart/driversComparisonChart.helpers.js +3 -1
  14. package/dist/esm/contexts/chat-context.js +51 -5
  15. package/dist/esm/index.js +1 -0
  16. package/dist/esm/types/src/components/ui/Chat/Chat.d.ts +1 -1
  17. package/dist/esm/types/src/components/ui/Chat/Chat.types.d.ts +12 -0
  18. package/dist/esm/types/src/components/ui/Chat/ChatChrome/ChatChrome.d.ts +1 -1
  19. package/dist/esm/types/src/components/ui/Chat/ChatChrome/ChatChrome.types.d.ts +4 -0
  20. package/dist/esm/types/src/components/ui/Chat/ChatMessage/ChatMessage.d.ts +1 -1
  21. package/dist/esm/types/src/components/ui/Chat/ChatSheet/ChatSelector.d.ts +2 -1
  22. package/dist/esm/types/src/components/ui/Chat/ChatSheet/ChatSheet.d.ts +1 -1
  23. package/dist/esm/types/src/components/ui/Chat/ChatSheet/useChatPanelChromeModel.d.ts +5 -3
  24. package/dist/esm/types/src/components/ui/Chat/index.d.ts +3 -1
  25. package/dist/esm/types/src/components/ui/FileChip/FileChip.types.d.ts +1 -1
  26. package/dist/esm/types/src/contexts/chat-context.d.ts +16 -4
  27. package/package.json +1 -1
  28. package/src/components/ui/Chat/Chat.styl +4 -1
  29. package/src/components/ui/Chat/Chat.tsx +2 -1
  30. package/src/components/ui/Chat/Chat.types.ts +14 -0
  31. package/src/components/ui/Chat/ChatChrome/ChatChrome.styl +20 -10
  32. package/src/components/ui/Chat/ChatChrome/ChatChrome.styl.d.ts +2 -0
  33. package/src/components/ui/Chat/ChatChrome/ChatChrome.tsx +27 -10
  34. package/src/components/ui/Chat/ChatChrome/ChatChrome.types.ts +4 -0
  35. package/src/components/ui/Chat/ChatEmptyState/ChatEmptyState.styl +1 -2
  36. package/src/components/ui/Chat/ChatMessage/ChatMessage.tsx +9 -1
  37. package/src/components/ui/Chat/ChatPresets/ChatPresets.styl +5 -4
  38. package/src/components/ui/Chat/ChatSheet/ChatSelector.tsx +3 -1
  39. package/src/components/ui/Chat/ChatSheet/ChatSheet.tsx +2 -0
  40. package/src/components/ui/Chat/ChatSheet/useChatPanelChromeModel.tsx +24 -5
  41. package/src/components/ui/Chat/buildChatSendMessagePayload.ts +2 -1
  42. package/src/components/ui/Chat/index.ts +4 -0
  43. package/src/components/ui/FileChip/FileChip.tsx +11 -0
  44. package/src/components/ui/FileChip/FileChip.types.ts +1 -1
  45. package/src/components/widgets/DriversComparisonChart/driversComparisonChart.helpers.test.ts +13 -4
  46. package/src/components/widgets/DriversComparisonChart/driversComparisonChart.helpers.ts +2 -1
  47. package/src/contexts/chat-context.tsx +80 -6
  48. package/src/docs/pages/ChatPage.styl +6 -0
  49. package/src/docs/pages/ChatPage.styl.d.ts +7 -0
  50. package/src/docs/pages/ChatPage.tsx +30 -87
  51. package/src/docs/pages/ChatSlashCommandsPage.tsx +4 -4
@@ -7,8 +7,8 @@ import { ChatMessage } from './ChatMessage/ChatMessage.js';
7
7
  import { ChatPrompt } from './ChatPrompt/ChatPrompt.js';
8
8
  import { ChatSelector } from './ChatSheet/ChatSelector.js';
9
9
 
10
- function Chat({ children, className, isEmpty, scopeId, onChatDeleted, onNewChat, ...props }) {
11
- return (jsxs("div", { className: cn(S.root, className, isEmpty && S.isEmpty), ...props, children: [scopeId ? (jsx("div", { className: S.header, children: jsx(ChatSelector, { id: scopeId, onChatDeleted: onChatDeleted, onNewChat: onNewChat }) })) : null, children] }));
10
+ function Chat({ children, className, isEmpty, scopeId, onChatDeleted, onNewChat, hideChatSelector = false, ...props }) {
11
+ return (jsxs("div", { className: cn(S.root, className, isEmpty && S.isEmpty), ...props, children: [scopeId && !hideChatSelector ? (jsx("div", { className: S.header, children: jsx(ChatSelector, { id: scopeId, onChatDeleted: onChatDeleted, onNewChat: onNewChat }) })) : null, children] }));
12
12
  }
13
13
  Chat.Prompt = ChatPrompt;
14
14
  Chat.Message = ChatMessage;
@@ -1,6 +1,6 @@
1
1
  import styleInject from 'style-inject';
2
2
 
3
- var css_248z = ".Chat_root__IWt99{background-color:var(--background);display:flex;flex-direction:column;height:100%;min-height:0}.Chat_header__ZjwP-{align-items:center;display:flex;flex-shrink:0;min-height:64px;padding:var(--p-2) var(--p-6) 0;padding-right:var(--p-12)}.Chat_isEmpty__b4ViB{padding-bottom:170px}";
3
+ var css_248z = ".Chat_root__IWt99{background-color:var(--background);display:flex;flex:1;flex-direction:column;height:100%;min-height:0}.Chat_header__ZjwP-{align-items:center;display:flex;flex-shrink:0;min-height:64px;padding:var(--p-2) var(--p-6) 0;padding-right:var(--p-12)}.Chat_isEmpty__b4ViB{flex:1;min-height:0;overflow:hidden}";
4
4
  var S = {"root":"Chat_root__IWt99","header":"Chat_header__ZjwP-","isEmpty":"Chat_isEmpty__b4ViB"};
5
5
  styleInject(css_248z);
6
6
 
@@ -1,4 +1,4 @@
1
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import cn from 'classnames';
3
3
  import { useMemo, useState, useCallback, useEffect } from 'react';
4
4
  import { displayLabelForBranchKeyFromMessages, humanizeBranchKey } from '../ChatMessage/presetScript.js';
@@ -10,17 +10,19 @@ import { DropZone } from '../../DropZone/DropZone.js';
10
10
  import { PanelResizeHandle } from '../../Sidebar/Sidebar.js';
11
11
  import SidebarStem from '../../Sidebar/Sidebar.styl.js';
12
12
  import { Chat } from '../Chat.js';
13
+ import { MessageRole } from '../Chat.types.js';
13
14
  import { filterToTextAttachments, isAttachmentsDropzoneEnabled, buildAcceptAttr } from '../chatAttachmentAccept.js';
14
15
  import { extractChatAttachmentItems } from '../chatAttachmentExtract.js';
15
16
  import S from './ChatChrome.styl.js';
16
17
 
17
- function ChatChrome({ showResizeHandle, resizeHandle, onClose, isEmpty, renderPresets, messages, onQuickReply, suppressedQuickReplyKeys, isLoading, loadingLabel, scriptContinueLabel, onScriptContinue, renderMessageChart, showSyntheticBranchButtons, unusedBranchKeys, showInlinePresets, isLastMessageFromUser, scrollRef, effectiveScopeId, onPromptSubmit, onChatDeleted, onNewChat, promptPrefill, footerClassName, emptyState, allowedAttachments, allowPdfAttachments = false, onAttachmentsDropped, slashCommandItems, onSlashItemCommand, promptPlaceholder, }) {
18
+ function ChatChrome({ showResizeHandle, resizeHandle, onClose, isEmpty, renderPresets, messages, onQuickReply, suppressedQuickReplyKeys, isLoading, loadingLabel, scriptContinueLabel, onScriptContinue, renderMessageChart, renderSystemMessage, showSyntheticBranchButtons, unusedBranchKeys, showInlinePresets, isLastMessageFromUser, scrollRef, effectiveScopeId, onPromptSubmit, onChatDeleted, onNewChat, promptPrefill, footerClassName, emptyState, allowedAttachments, allowPdfAttachments = false, onAttachmentsDropped, slashCommandItems, onSlashItemCommand, promptPlaceholder, hideChatSelector = false, }) {
18
19
  const filteredAllowedAttachments = useMemo(() => filterToTextAttachments(allowedAttachments), [allowedAttachments]);
19
20
  const attachmentsDropzoneEnabled = isAttachmentsDropzoneEnabled(allowedAttachments, allowPdfAttachments);
20
21
  const attachmentAccept = useMemo(() => buildAcceptAttr(filteredAllowedAttachments, allowPdfAttachments), [filteredAllowedAttachments, allowPdfAttachments]);
21
22
  const [pendingAttachments, setPendingAttachments] = useState([]);
22
23
  const [isExtractingAttachments, setIsExtractingAttachments] = useState(false);
23
24
  const promptDisabled = isExtractingAttachments;
25
+ const hasInProgressSystemMessage = useMemo(() => messages.some(msg => msg.role === MessageRole.SYSTEM && msg.inProgress === true), [messages]);
24
26
  const handleAttachmentFiles = useCallback((files) => {
25
27
  if (promptDisabled || files.length === 0)
26
28
  return;
@@ -74,18 +76,20 @@ function ChatChrome({ showResizeHandle, resizeHandle, onClose, isEmpty, renderPr
74
76
  showInlinePresets,
75
77
  showSyntheticBranchButtons,
76
78
  ]);
77
- return (jsxs("div", { className: S.root, children: [showResizeHandle && resizeHandle ? (jsx(PanelResizeHandle, { edge: "leading", isActive: resizeHandle.isActive, startWidthPx: resizeHandle.startWidthPx, getShellWidth: resizeHandle.getShellWidth, onDragWidth: resizeHandle.onDragWidth, onDragComplete: resizeHandle.onDragComplete, className: cn(SidebarStem.sidebarResizeHandle, S.chatResizeHandle) })) : null, jsx("div", { className: S.panelHeader, children: onClose ? (jsx(Button, { type: "button", variant: "ghost", icon: true, className: S.panelClose, "aria-label": "Close chat", onClick: onClose, children: jsx(X, { className: "size-4" }) })) : null }), jsxs("div", { className: S.content, children: [attachmentsDropzoneEnabled ? (jsx(DropZone, { accept: attachmentAccept, label: "Drop text files to attach", multiple: true, ghost: true, overlayScope: "container", disabled: promptDisabled, className: S.attachmentDropzone, onFiles: handleAttachmentFiles })) : null, jsxs(Chat, { isEmpty: isEmpty, scopeId: effectiveScopeId, onChatDeleted: onChatDeleted, onNewChat: onNewChat, children: [isEmpty ? (jsxs(Fragment, { children: [jsx(Chat.EmptyState, { ...emptyState }), renderPresets('fixed')] })) : (jsx("div", { className: S.scrollWrapper, children: jsxs(Scroll, { y: true, yScrollbarClassName: S.scrollbar, className: S.scroll, innerClassName: S.scrollInner, offset: { y: { before: 56, after: 180 } }, fadeSize: "m", autoHide: true, ref: scrollRef, children: [messages.map((msg, index, arr) => {
79
+ return (jsxs("div", { className: S.root, children: [showResizeHandle && resizeHandle ? (jsx(PanelResizeHandle, { edge: "leading", isActive: resizeHandle.isActive, startWidthPx: resizeHandle.startWidthPx, getShellWidth: resizeHandle.getShellWidth, onDragWidth: resizeHandle.onDragWidth, onDragComplete: resizeHandle.onDragComplete, className: cn(SidebarStem.sidebarResizeHandle, S.chatResizeHandle) })) : null, jsx("div", { className: S.panelHeader, children: onClose ? (jsx(Button, { type: "button", variant: "ghost", icon: true, className: S.panelClose, "aria-label": "Close chat", onClick: onClose, children: jsx(X, { className: "size-4" }) })) : null }), jsxs("div", { className: S.content, children: [attachmentsDropzoneEnabled ? (jsx(DropZone, { accept: attachmentAccept, label: "Drop text files to attach", multiple: true, ghost: true, overlayScope: "container", disabled: promptDisabled, className: S.attachmentDropzone, onFiles: handleAttachmentFiles })) : null, jsxs(Chat, { isEmpty: isEmpty, scopeId: effectiveScopeId, onChatDeleted: onChatDeleted, onNewChat: onNewChat, hideChatSelector: hideChatSelector, children: [isEmpty ? (jsx("div", { className: S.emptyBody, children: jsx(Chat.EmptyState, { ...emptyState }) })) : (jsx("div", { className: S.scrollWrapper, children: jsxs(Scroll, { y: true, yScrollbarClassName: S.scrollbar, className: S.scroll, innerClassName: S.scrollInner, offset: { y: { before: 56, after: 24 } }, fadeSize: "m", autoHide: true, ref: scrollRef, children: [messages.map((msg, index, arr) => {
78
80
  const isLast = index === arr.length - 1;
79
- return (jsx(Chat.Message, { role: msg.role, text: msg.text, inProgress: msg.inProgress, userTextFileAttachments: msg.userTextFileAttachments, onQuickReply: onQuickReply, suppressedQuickReplyKeys: suppressedQuickReplyKeys, quickReplyDisabled: isLoading, quickReplyHidden: Boolean(loadingLabel), isLastMessage: isLast, scriptContinue: isLast && scriptContinueLabel
81
+ return (jsx(Chat.Message, { message: msg, role: msg.role, text: msg.text, inProgress: msg.inProgress, userTextFileAttachments: msg.userTextFileAttachments, onQuickReply: onQuickReply, suppressedQuickReplyKeys: suppressedQuickReplyKeys, quickReplyDisabled: isLoading, quickReplyHidden: Boolean(loadingLabel), isLastMessage: isLast, scriptContinue: isLast && scriptContinueLabel
80
82
  ? { label: scriptContinueLabel }
81
83
  : undefined, onScriptContinue: isLast && scriptContinueLabel
82
84
  ? onScriptContinue
83
- : undefined, renderMessageChart: renderMessageChart }, msg.id));
85
+ : undefined, renderMessageChart: renderMessageChart, renderSystemMessage: renderSystemMessage }, msg.id));
84
86
  }), showSyntheticBranchButtons ? (jsx("div", { className: S.branchRow, children: unusedBranchKeys.map(key => {
85
87
  const label = displayLabelForBranchKeyFromMessages(key, messages) ??
86
88
  humanizeBranchKey(key);
87
89
  return (jsx("span", { className: S.branchBtnWrap, children: jsxs(Button, { type: "button", variant: "outline", size: "sm", disabled: isLoading, onClick: () => onQuickReply(key, label), children: [jsx(PaperPlaneRightIcon, {}), label] }) }, key));
88
- }) })) : null, showInlinePresets && renderPresets('inline'), isLoading && (isLastMessageFromUser || loadingLabel) && (jsx(TextShimmer, { duration: 1, spread: 5, className: S.loader, children: loadingLabel ?? 'Thinking...' }))] }) })), jsxs("div", { className: cn(S.footer, footerClassName), children: [isEmpty ? (jsx("div", { className: S.notice, children: "Forecast Assistant can make mistakes." })) : null, jsx(Chat.Prompt, { onSubmit: handlePromptSubmitWithAttachments, disabled: promptDisabled, attachments: pendingAttachments, onRemoveAttachment: handleRemoveAttachment, prefillMessage: promptPrefill ?? undefined, placeholder: promptPlaceholder, slashCommandItems: slashCommandItems, onSlashItemCommand: onSlashItemCommand, attachmentAccept: attachmentsDropzoneEnabled ? attachmentAccept : undefined, onAttachmentFiles: attachmentsDropzoneEnabled ? handleAttachmentFiles : undefined })] })] })] })] }));
90
+ }) })) : null, showInlinePresets && renderPresets('inline'), isLoading &&
91
+ !hasInProgressSystemMessage &&
92
+ (isLastMessageFromUser || loadingLabel) && (jsx(TextShimmer, { duration: 1, spread: 5, className: S.loader, children: loadingLabel ?? 'Thinking...' }))] }) })), isEmpty ? (jsx("div", { className: S.fixedPresets, children: renderPresets('fixed') })) : null, jsxs("div", { className: cn(S.footer, footerClassName), children: [isEmpty ? (jsx("div", { className: S.notice, children: "Forecast Assistant can make mistakes." })) : null, jsx(Chat.Prompt, { onSubmit: handlePromptSubmitWithAttachments, disabled: promptDisabled, attachments: pendingAttachments, onRemoveAttachment: handleRemoveAttachment, prefillMessage: promptPrefill ?? undefined, placeholder: promptPlaceholder, slashCommandItems: slashCommandItems, onSlashItemCommand: onSlashItemCommand, attachmentAccept: attachmentsDropzoneEnabled ? attachmentAccept : undefined, onAttachmentFiles: attachmentsDropzoneEnabled ? handleAttachmentFiles : undefined })] })] })] })] }));
89
93
  }
90
94
 
91
95
  export { ChatChrome };
@@ -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)}}.ChatChrome_root__oh4Ay{border-radius:var(--p-4);display:flex;flex-direction:column;height:100%;min-height:0;overflow:hidden;position:relative}.ChatChrome_chatResizeHandle__epfiT{background-color:var(--page-color);border-radius:2.5px;height:calc(100vh + 200px);left:0;opacity:0;position:absolute;right:auto;top:-200px;touch-action:none;transition:opacity .15s ease-out;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:5px;z-index:30}.ChatChrome_chatResizeHandle__epfiT:before{left:0;right:auto}.ChatChrome_panelHeader__Hkfit{align-items:center;display:flex;flex-shrink:0;justify-content:flex-end;min-height:70px;padding:var(--p-2) var(--p-3);position:absolute;width:100%;z-index:100}.ChatChrome_panelClose__DbKxz{flex-shrink:0}.ChatChrome_content__5qFEi{display:flex;flex:1;flex-direction:column;min-height:0;position:relative}.ChatChrome_attachmentDropzone__OC8UI{inset:0;position:absolute;z-index:200}.ChatChrome_scrollWrapper__m4HMu{flex:1;min-height:0;position:relative}.ChatChrome_scroll__oCxoJ{align-items:flex-end;height:100%;max-height:100%;max-width:100%;padding-bottom:var(--p-2);position:absolute;width:100%;z-index:3}.ChatChrome_scrollbar__Hu0aG{right:0!important}.ChatChrome_scrollInner__K9hIy{min-height:100%;padding-top:var(--p-10)}.ChatChrome_scrollInner__K9hIy:after{content:\"\";display:block;height:260px}.ChatChrome_footer__a5Bpp{backdrop-filter:blur(30px);background-color:var(--background-alpha-800);border-top:1px solid var(--border);bottom:0;box-shadow:0 0 20px 16px var(--background);display:flex;flex-direction:column;position:absolute;width:100%;z-index:50}.ChatChrome_notice__JACIw{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-7)*-1)}@media (max-width:768px){.ChatChrome_notice__JACIw{font-size:10px}}.ChatChrome_loader__9-lnf{color:var(--muted-foreground);margin:var(--p-2) var(--p-6) var(--p-10)}.ChatChrome_branchRow__NMDNv{display:flex;flex-wrap:wrap;gap:8px;margin-top:var(--p-6);padding:0 var(--p-6);width:100%}.ChatChrome_branchBtnWrap__aOSVP{display:inline-flex;vertical-align:middle}";
4
- var S = {"root":"ChatChrome_root__oh4Ay","chatResizeHandle":"ChatChrome_chatResizeHandle__epfiT","panelHeader":"ChatChrome_panelHeader__Hkfit","panelClose":"ChatChrome_panelClose__DbKxz","content":"ChatChrome_content__5qFEi","attachmentDropzone":"ChatChrome_attachmentDropzone__OC8UI","scrollWrapper":"ChatChrome_scrollWrapper__m4HMu","scroll":"ChatChrome_scroll__oCxoJ","scrollbar":"ChatChrome_scrollbar__Hu0aG","scrollInner":"ChatChrome_scrollInner__K9hIy","footer":"ChatChrome_footer__a5Bpp","notice":"ChatChrome_notice__JACIw","loader":"ChatChrome_loader__9-lnf","branchRow":"ChatChrome_branchRow__NMDNv","branchBtnWrap":"ChatChrome_branchBtnWrap__aOSVP"};
3
+ var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.ChatChrome_root__oh4Ay{border-radius:var(--p-4);display:flex;flex-direction:column;height:100%;min-height:0;overflow:hidden;position:relative}.ChatChrome_chatResizeHandle__epfiT{background-color:var(--page-color);border-radius:2.5px;height:calc(100vh + 200px);left:0;opacity:0;position:absolute;right:auto;top:-200px;touch-action:none;transition:opacity .15s ease-out;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:5px;z-index:30}.ChatChrome_chatResizeHandle__epfiT:before{left:0;right:auto}.ChatChrome_panelHeader__Hkfit{align-items:center;display:flex;flex-shrink:0;justify-content:flex-end;min-height:70px;padding:var(--p-2) var(--p-3);position:absolute;width:100%;z-index:100}.ChatChrome_panelClose__DbKxz{flex-shrink:0}.ChatChrome_content__5qFEi{display:flex;flex:1;flex-direction:column;min-height:0;position:relative}.ChatChrome_attachmentDropzone__OC8UI{inset:0;position:absolute;z-index:200}.ChatChrome_scrollWrapper__m4HMu{flex:1;min-height:0;position:relative}.ChatChrome_scroll__oCxoJ{align-items:flex-end;height:100%;max-height:100%;max-width:100%;padding-bottom:var(--p-2);position:absolute;width:100%;z-index:3}.ChatChrome_scrollbar__Hu0aG{right:0!important}.ChatChrome_scrollInner__K9hIy{min-height:100%;padding-top:var(--p-10)}.ChatChrome_emptyBody__f2NE8{display:flex;flex:1;flex-direction:column;min-height:0;overflow:hidden}.ChatChrome_emptyBody__f2NE8>*{flex:1;min-height:0;overflow:auto}.ChatChrome_footer__a5Bpp{backdrop-filter:blur(30px);background-color:var(--background-alpha-800);border-top:1px solid var(--border);box-shadow:0 8px 24px 0 var(--background);display:flex;flex-direction:column;flex-shrink:0;position:relative;width:100%;z-index:50}.ChatChrome_fixedPresets__bONhR{flex-shrink:0;position:relative;width:100%;z-index:10}.ChatChrome_notice__JACIw{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-7)*-1)}@media (max-width:768px){.ChatChrome_notice__JACIw{font-size:10px}}.ChatChrome_loader__9-lnf{color:var(--muted-foreground);margin:var(--p-2) var(--p-6) var(--p-10)}.ChatChrome_branchRow__NMDNv{display:flex;flex-wrap:wrap;gap:8px;margin-top:var(--p-6);padding:0 var(--p-6);width:100%}.ChatChrome_branchBtnWrap__aOSVP{display:inline-flex;vertical-align:middle}";
4
+ var S = {"root":"ChatChrome_root__oh4Ay","chatResizeHandle":"ChatChrome_chatResizeHandle__epfiT","panelHeader":"ChatChrome_panelHeader__Hkfit","panelClose":"ChatChrome_panelClose__DbKxz","content":"ChatChrome_content__5qFEi","attachmentDropzone":"ChatChrome_attachmentDropzone__OC8UI","scrollWrapper":"ChatChrome_scrollWrapper__m4HMu","scroll":"ChatChrome_scroll__oCxoJ","scrollbar":"ChatChrome_scrollbar__Hu0aG","scrollInner":"ChatChrome_scrollInner__K9hIy","emptyBody":"ChatChrome_emptyBody__f2NE8","footer":"ChatChrome_footer__a5Bpp","fixedPresets":"ChatChrome_fixedPresets__bONhR","notice":"ChatChrome_notice__JACIw","loader":"ChatChrome_loader__9-lnf","branchRow":"ChatChrome_branchRow__NMDNv","branchBtnWrap":"ChatChrome_branchBtnWrap__aOSVP"};
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 = ".ChatEmptyState_root__j1n-C{align-items:center;color:var(--text-secondary);display:flex;flex:1;flex-direction:column;font-size:var(--text-sm);gap:var(--p-10);justify-content:center;padding:var(--p-6);text-align:center;text-wrap:balance}.ChatEmptyState_icon__YSDgv,.ChatEmptyState_icon__YSDgv>svg{height:32px;width:32px}";
3
+ var css_248z = ".ChatEmptyState_root__j1n-C{align-items:center;color:var(--text-secondary);display:flex;flex:1;flex-direction:column;font-size:var(--text-sm);gap:var(--p-10);justify-content:center;min-height:0;padding:var(--p-6);text-align:center;text-wrap:balance}.ChatEmptyState_icon__YSDgv,.ChatEmptyState_icon__YSDgv>svg{height:32px;width:32px}";
4
4
  var S = {"root":"ChatEmptyState_root__j1n-C","icon":"ChatEmptyState_icon__YSDgv"};
5
5
  styleInject(css_248z);
6
6
 
@@ -10,13 +10,13 @@ import { AgentMessageContent } from './AgentMessageContent.js';
10
10
  import S from './ChatMessage.styl.js';
11
11
  import { UserTextFileAttachmentBubble } from './UserTextFileAttachmentBubble.js';
12
12
 
13
- function ChatMessage({ role, text, inProgress, userTextFileAttachments, onQuickReply, suppressedQuickReplyKeys, quickReplyDisabled, quickReplyHidden, isLastMessage = true, scriptContinue, onScriptContinue, renderMessageChart, }) {
13
+ function ChatMessage({ role, text, inProgress, userTextFileAttachments, onQuickReply, suppressedQuickReplyKeys, quickReplyDisabled, quickReplyHidden, isLastMessage = true, scriptContinue, onScriptContinue, renderMessageChart, message, renderSystemMessage, }) {
14
14
  const fileAttachments = userTextFileAttachmentsFromMessage({
15
15
  userTextFileAttachments,
16
16
  });
17
17
  const isAssistant = role === MessageRole.ASSISTANT;
18
18
  const isSystem = role === MessageRole.SYSTEM;
19
- return (jsx("div", { className: cn(S.root, S[`role-${role}`]), children: isSystem ? (jsx("div", { className: S.text, children: inProgress ? jsx(TextShimmer, { as: "span", children: text }) : text })) : isAssistant ? (jsx(AgentMessageContent, { text: text, onQuickReply: onQuickReply, suppressedQuickReplyKeys: suppressedQuickReplyKeys, quickReplyDisabled: quickReplyDisabled, quickReplyHidden: quickReplyHidden, isLastMessage: isLastMessage, scriptContinue: scriptContinue, onScriptContinue: onScriptContinue, renderMessageChart: renderMessageChart })) : (jsxs("div", { className: S.userColumn, children: [jsx("div", { className: S.text, children: jsx(InteractiveContent, { text: text }) }), fileAttachments.map(attachment => (jsx(UserTextFileAttachmentBubble, { attachment: attachment }, `${attachment.displayName}:${attachment.filename}`)))] })) }));
19
+ return (jsx("div", { className: cn(S.root, S[`role-${role}`]), children: isSystem ? (jsx("div", { className: S.text, children: inProgress ? (jsx(TextShimmer, { as: "span", children: text })) : renderSystemMessage && message ? (renderSystemMessage(message)) : (text) })) : isAssistant ? (jsx(AgentMessageContent, { text: text, onQuickReply: onQuickReply, suppressedQuickReplyKeys: suppressedQuickReplyKeys, quickReplyDisabled: quickReplyDisabled, quickReplyHidden: quickReplyHidden, isLastMessage: isLastMessage, scriptContinue: scriptContinue, onScriptContinue: onScriptContinue, renderMessageChart: renderMessageChart })) : (jsxs("div", { className: S.userColumn, children: [jsx("div", { className: S.text, children: jsx(InteractiveContent, { text: text }) }), fileAttachments.map(attachment => (jsx(UserTextFileAttachmentBubble, { attachment: attachment }, `${attachment.displayName}:${attachment.filename}`)))] })) }));
20
20
  }
21
21
 
22
22
  export { ChatMessage };
@@ -1,6 +1,6 @@
1
1
  import styleInject from 'style-inject';
2
2
 
3
- var css_248z = ".ChatPresets_root__Cj42o{bottom:160px;width:100%}.ChatPresets_inlineRoot__WXVnu{margin-top:var(--p-6);position:relative;width:100%}.ChatPresets_inner__h14-q{background-color:var(--background);display:flex;flex-wrap:wrap;gap:8px;padding:var(--p-2) var(--p-6) var(--p-3)}.ChatPresets_innerInline__iPM2b{background-color:transparent}.ChatPresets_item__LfX5b{flex-shrink:0;font-size:var(--text-xs);height:auto;line-height:1.4;max-width:300px;min-height:auto;min-width:0;overflow-wrap:anywhere;padding:var(--p-3);text-align:left;white-space:break-spaces!important}";
3
+ var css_248z = ".ChatPresets_root__Cj42o{flex-shrink:0;position:relative;width:100%}.ChatPresets_inlineRoot__WXVnu{margin-top:var(--p-6);position:relative;width:100%}.ChatPresets_inner__h14-q{background-color:var(--background);display:flex;flex-wrap:wrap;gap:8px;min-width:0;padding:var(--p-2) var(--p-6) var(--p-3)}.ChatPresets_innerInline__iPM2b{background-color:transparent}.ChatPresets_item__LfX5b{flex:0 1 auto;font-size:var(--text-xs);height:auto;line-height:1.4;max-width:min(300px,100%);min-height:auto;min-width:0;overflow-wrap:anywhere;padding:var(--p-3);text-align:left;white-space:break-spaces!important}";
4
4
  var S = {"root":"ChatPresets_root__Cj42o","inlineRoot":"ChatPresets_inlineRoot__WXVnu","inner":"ChatPresets_inner__h14-q","innerInline":"ChatPresets_innerInline__iPM2b","item":"ChatPresets_item__LfX5b"};
5
5
  styleInject(css_248z);
6
6
 
@@ -6,7 +6,7 @@ import { Button } from '../../Button/Button.js';
6
6
  import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '../../Select/Select.js';
7
7
  import S from './ChatSelector.styl.js';
8
8
 
9
- function ChatSelector({ id, className, onChatDeleted, onNewChat, }) {
9
+ function ChatSelector({ id, wrapperClassName, className, onChatDeleted, onNewChat, }) {
10
10
  const { chats, currentChatId, setCurrentChatId, newChat, deleteChat } = useChatsForScopeId(id);
11
11
  const handleValueChange = (value) => {
12
12
  if (value === 'new') {
@@ -39,7 +39,7 @@ function ChatSelector({ id, className, onChatDeleted, onNewChat, }) {
39
39
  }
40
40
  return 'New chat';
41
41
  };
42
- return (jsxs("div", { className: S.wrapper, children: [jsx("div", { className: S.selectGrow, children: jsxs(Select, { variant: "clear", value: currentChatId?.toString() ?? '', onValueChange: handleValueChange, children: [jsx(SelectTrigger, { size: "sm", className: className ? cn(S.selectTrigger, className) : S.selectTrigger, children: jsx(SelectValue, { placeholder: "Select chat" }) }), jsxs(SelectContent, { children: [jsx(SelectItem, { value: "new", children: "+ New Chat" }), chats.map(chat => (jsx(SelectItem, { value: chat.session_id.toString(), selected: chat.session_id === currentChatId, children: getChatDisplayName(chat) }, chat.session_id)))] })] }) }), currentChatId && chats.length > 0 && (jsx(Button, { type: "button", variant: "ghost", size: "sm", className: S.deleteBtn, "aria-label": "Delete chat", onClick: handleDeleteChat, children: jsx(Trash2Icon, { size: 16 }) }))] }));
42
+ return (jsxs("div", { className: cn(S.wrapper, wrapperClassName), children: [jsx("div", { className: S.selectGrow, children: jsxs(Select, { variant: "clear", value: currentChatId?.toString() ?? '', onValueChange: handleValueChange, children: [jsx(SelectTrigger, { size: "sm", className: className ? cn(S.selectTrigger, className) : S.selectTrigger, children: jsx(SelectValue, { placeholder: "Select chat" }) }), jsxs(SelectContent, { children: [jsx(SelectItem, { value: "new", children: "+ New Chat" }), chats.map(chat => (jsx(SelectItem, { value: chat.session_id.toString(), selected: chat.session_id === currentChatId, children: getChatDisplayName(chat) }, chat.session_id)))] })] }) }), currentChatId && chats.length > 0 && (jsx(Button, { type: "button", variant: "ghost", size: "sm", className: S.deleteBtn, "aria-label": "Delete chat", onClick: handleDeleteChat, children: jsx(Trash2Icon, { size: 16 }) }))] }));
43
43
  }
44
44
 
45
45
  export { ChatSelector };
@@ -4,7 +4,7 @@ import { Button } from '../../Button/Button.js';
4
4
  import { ChatChrome } from '../ChatChrome/ChatChrome.js';
5
5
  import { useChatPanelChromeModel } from './useChatPanelChromeModel.js';
6
6
 
7
- function ChatSheet({ triggerLabel = 'Open Chat', triggerAriaLabel, actionsRef, renderTrigger, presets, scopeId, onMessage, onScriptComplete, renderMessageChart, emptyState, allowedAttachments, allowPdfAttachments, onAttachmentsDropped, slashCommandItems, onSlashItemCommand, transformSendPayload, inline = false, }) {
7
+ function ChatSheet({ triggerLabel = 'Open Chat', triggerAriaLabel, actionsRef, renderTrigger, presets, scopeId, onMessage, onScriptComplete, renderMessageChart, renderSystemMessage, emptyState, allowedAttachments, allowPdfAttachments, onAttachmentsDropped, slashCommandItems, onSlashItemCommand, transformSendPayload, inline = false, }) {
8
8
  const model = useChatPanelChromeModel({
9
9
  embedAsPage: inline,
10
10
  presets,
@@ -12,6 +12,7 @@ function ChatSheet({ triggerLabel = 'Open Chat', triggerAriaLabel, actionsRef, r
12
12
  onMessage,
13
13
  onScriptComplete,
14
14
  renderMessageChart,
15
+ renderSystemMessage,
15
16
  emptyState,
16
17
  allowedAttachments,
17
18
  allowPdfAttachments,
@@ -22,7 +22,7 @@ const CHAT_NEW_SHORTCUT_KEY = 'o';
22
22
  const CHAT_QUERY_PARAM = 'chat';
23
23
  const CHAT_OPEN_VALUE = 'open';
24
24
  const PROMPT_QUERY_PARAM = 'prompt';
25
- function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onScriptComplete, renderMessageChart, emptyState, allowedAttachments, allowPdfAttachments, onAttachmentsDropped, slashCommandItems, onSlashItemCommand, copyHistoryOnNewChat = false, transformSendPayload, }) {
25
+ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onScriptComplete, renderMessageChart, renderSystemMessage, emptyState, allowedAttachments, allowPdfAttachments, onAttachmentsDropped, slashCommandItems, onSlashItemCommand, copyHistoryOnNewChat = false, transformSendPayload, }) {
26
26
  const effectiveScopeId = scopeId ?? NO_SCOPE_FALLBACK;
27
27
  const isMobile = useIsMobile();
28
28
  const { chatPanelContainer, isOpen: sidebarNavOpen, setOpen: setSidebarNavOpen, sidebarWidthPx, chatWidthPx, setChatWidthPx, getShellWidth, chatPanelOpen: shellChatPanelOpen, setChatPanelOpen, } = useSidebar();
@@ -336,8 +336,8 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
336
336
  }
337
337
  setOutboundLoadingLabel(loadingLabelFromSendPayload(payload));
338
338
  try {
339
- const assistantResponse = await sendMessage(payload);
340
- onMessage?.(displayTextFromSendPayload(payload), assistantResponse);
339
+ const { response: assistantResponse, sessionId } = await sendMessage(payload);
340
+ onMessage?.(displayTextFromSendPayload(payload), assistantResponse, sessionId);
341
341
  }
342
342
  finally {
343
343
  setOutboundLoadingLabel(undefined);
@@ -474,8 +474,8 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
474
474
  }
475
475
  setOutboundLoadingLabel(loadingLabelFromSendPayload(payload));
476
476
  try {
477
- const assistantResponse = await sendMessage(payload);
478
- onMessage?.(displayTextFromSendPayload(payload), assistantResponse);
477
+ const { response: assistantResponse, sessionId } = await sendMessage(payload);
478
+ onMessage?.(displayTextFromSendPayload(payload), assistantResponse, sessionId);
479
479
  }
480
480
  finally {
481
481
  setOutboundLoadingLabel(undefined);
@@ -916,6 +916,7 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
916
916
  scriptContinueLabel,
917
917
  onScriptContinue,
918
918
  renderMessageChart,
919
+ renderSystemMessage,
919
920
  showSyntheticBranchButtons,
920
921
  unusedBranchKeys,
921
922
  showInlinePresets,
@@ -68,7 +68,9 @@ function displayTextFromSendPayload(message) {
68
68
  }
69
69
  /** Optional loading shimmer label from a structured send payload. */
70
70
  function loadingLabelFromSendPayload(message) {
71
- return typeof message === 'string' ? undefined : message.loadingLabel;
71
+ if (typeof message === 'string')
72
+ return undefined;
73
+ return message.loadingLabel ?? message.systemProgressLabel;
72
74
  }
73
75
 
74
76
  export { buildChatSendMessagePayload, displayTextFromSendPayload, loadingLabelFromSendPayload, normalizeUserTextFileAttachments };
@@ -3,6 +3,7 @@ import cn from 'classnames';
3
3
  import { CsvIcon } from '../../icons/CsvIcon/CsvIcon.js';
4
4
  import { CardHeader, CardAction } from '../Card/Card.js';
5
5
  import { XIcon, FileTextIcon } from '@phosphor-icons/react';
6
+ import { LayoutDashboard } from 'lucide-react';
6
7
  import S from './FileChip.styl.js';
7
8
 
8
9
  const FORMAT_ICON_SIZE = 32;
@@ -10,6 +11,9 @@ function FormatIcon({ format }) {
10
11
  if (format === 'csv') {
11
12
  return jsx(CsvIcon, { size: FORMAT_ICON_SIZE });
12
13
  }
14
+ if (format === 'dashboard') {
15
+ return (jsx(LayoutDashboard, { size: FORMAT_ICON_SIZE, "aria-hidden": true, style: { color: 'var(--muted-foreground)' } }));
16
+ }
13
17
  return (jsx(FileTextIcon, { size: FORMAT_ICON_SIZE, "aria-hidden": true, style: { color: 'var(--muted-foreground)' } }));
14
18
  }
15
19
  function FileChipHeader({ name, format, hint, onRemove, }) {
@@ -97,12 +97,14 @@ function shiftNormalizedSeriesBackward(series, lagMonths) {
97
97
  function applyDriversComparisonViewToPayload(payload, tab) {
98
98
  if (!payload || tab === 'lagged')
99
99
  return payload;
100
+ if (!payload.target?.normalized_series)
101
+ return payload;
100
102
  return {
101
103
  target: {
102
104
  ...payload.target,
103
105
  normalized_series: { ...payload.target.normalized_series },
104
106
  },
105
- drivers: payload.drivers.map(driver => {
107
+ drivers: (payload.drivers ?? []).map(driver => {
106
108
  const lagMonths = parseLagMonthsFromLabel(resolveDriverLagLabel(driver));
107
109
  const series = driver.normalized_series ?? {};
108
110
  const normalized_series = lagMonths != null && lagMonths > 0
@@ -86,6 +86,7 @@ function cloneMessagesForNewSession(messages) {
86
86
  .filter(message => !message.inProgress)
87
87
  .map(message => ({
88
88
  ...message,
89
+ ...(message.meta ? { meta: { ...message.meta } } : {}),
89
90
  ...(message.userTextFileAttachments
90
91
  ? {
91
92
  userTextFileAttachments: message.userTextFileAttachments.map(attachment => ({ ...attachment })),
@@ -217,6 +218,7 @@ function ChatProvider({ children, userSwitchKey, sendChatMessage: sendChatMessag
217
218
  ? { userTextFileAttachments: attachments }
218
219
  : {}),
219
220
  ...(options?.inProgress ? { inProgress: true } : {}),
221
+ ...(options?.meta ? { meta: { ...options.meta } } : {}),
220
222
  };
221
223
  setChats(prev => {
222
224
  const scopeChats = prev[scopeId] ?? [];
@@ -277,6 +279,9 @@ function ChatProvider({ children, userSwitchKey, sendChatMessage: sendChatMessag
277
279
  (patch.role != null && patch.role !== MessageRole.SYSTEM)) {
278
280
  delete next.inProgress;
279
281
  }
282
+ if (patch.meta != null) {
283
+ next.meta = { ...next.meta, ...patch.meta };
284
+ }
280
285
  return next;
281
286
  }),
282
287
  };
@@ -290,7 +295,10 @@ function ChatProvider({ children, userSwitchKey, sendChatMessage: sendChatMessag
290
295
  if (userSwitchKey === null)
291
296
  return;
292
297
  addScopeIdToRegistry(scopeId);
293
- const cloned = messages.map(message => ({ ...message }));
298
+ const cloned = messages.map(message => ({
299
+ ...message,
300
+ ...(message.meta ? { meta: { ...message.meta } } : {}),
301
+ }));
294
302
  setChats(prev => {
295
303
  const scopeChats = prev[scopeId] ?? [];
296
304
  const updatedChats = scopeChats.map(chat => {
@@ -303,6 +311,24 @@ function ChatProvider({ children, userSwitchKey, sendChatMessage: sendChatMessag
303
311
  return { ...prev, [scopeId]: updatedChats };
304
312
  });
305
313
  }, [userSwitchKey]);
314
+ const updateChatMeta = useCallback((scopeId, chatId, patch) => {
315
+ if (userSwitchKey === null)
316
+ return;
317
+ setChats(prev => {
318
+ const scopeChats = prev[scopeId] ?? [];
319
+ const updatedChats = scopeChats.map(chat => {
320
+ if (chat.session_id !== chatId)
321
+ return chat;
322
+ return {
323
+ ...chat,
324
+ meta: { ...chat.meta, ...patch },
325
+ };
326
+ });
327
+ const chatsKey = getChatsKey(scopeId);
328
+ LS.set(chatsKey, updatedChats);
329
+ return { ...prev, [scopeId]: updatedChats };
330
+ });
331
+ }, [userSwitchKey]);
306
332
  const sendMessage = useCallback(async (scopeId, message, chatId) => {
307
333
  const targetChatId = chatId ?? getCurrentChatId(scopeId);
308
334
  if (targetChatId === null || targetChatId === '') {
@@ -317,10 +343,19 @@ function ChatProvider({ children, userSwitchKey, sendChatMessage: sendChatMessag
317
343
  userTextFileAttachments: normalizeUserTextFileAttachments(message),
318
344
  });
319
345
  }
346
+ let progressMessageId;
347
+ if (typeof message !== 'string' && message.systemProgressLabel) {
348
+ progressMessageId = addMessage(scopeId, targetChatId, MessageRole.SYSTEM, message.systemProgressLabel, { inProgress: true });
349
+ }
320
350
  const pendingChatSessionId = targetChatId;
321
351
  beginOutboundPending(scopeId, pendingChatSessionId);
352
+ let effectiveSessionId = pendingChatSessionId;
322
353
  try {
323
354
  const data = await sendChatMessageFn(apiPayload, pendingChatSessionId);
355
+ effectiveSessionId =
356
+ data.session_id && data.session_id !== pendingChatSessionId
357
+ ? data.session_id
358
+ : pendingChatSessionId;
324
359
  if (data.session_id && data.session_id !== pendingChatSessionId) {
325
360
  setChats(prev => {
326
361
  const scopeChats = prev[scopeId] ?? [];
@@ -333,14 +368,21 @@ function ChatProvider({ children, userSwitchKey, sendChatMessage: sendChatMessag
333
368
  });
334
369
  setCurrentChatId(scopeId, data.session_id);
335
370
  }
336
- addMessage(scopeId, data.session_id ? data.session_id : pendingChatSessionId, MessageRole.ASSISTANT, data.response);
337
- return data.response;
371
+ if (progressMessageId) {
372
+ removeMessageById(scopeId, effectiveSessionId, progressMessageId);
373
+ progressMessageId = undefined;
374
+ }
375
+ addMessage(scopeId, effectiveSessionId, MessageRole.ASSISTANT, data.response);
376
+ return { response: data.response, sessionId: effectiveSessionId };
338
377
  }
339
378
  catch (error) {
379
+ if (progressMessageId) {
380
+ removeMessageById(scopeId, effectiveSessionId, progressMessageId);
381
+ }
340
382
  const errorMessage = error instanceof Error
341
383
  ? error.message
342
384
  : 'Sorry, I encountered an error processing your message. Please try again.';
343
- addMessage(scopeId, pendingChatSessionId, MessageRole.ASSISTANT, errorMessage);
385
+ addMessage(scopeId, effectiveSessionId, MessageRole.ASSISTANT, errorMessage);
344
386
  throw error;
345
387
  }
346
388
  finally {
@@ -348,6 +390,7 @@ function ChatProvider({ children, userSwitchKey, sendChatMessage: sendChatMessag
348
390
  }
349
391
  }, [
350
392
  addMessage,
393
+ removeMessageById,
351
394
  beginOutboundPending,
352
395
  endOutboundPending,
353
396
  getCurrentChatId,
@@ -381,6 +424,7 @@ function ChatProvider({ children, userSwitchKey, sendChatMessage: sendChatMessag
381
424
  removeMessageById,
382
425
  updateMessageById,
383
426
  setChatMessages,
427
+ updateChatMeta,
384
428
  sendMessage,
385
429
  getChatsForScopeId,
386
430
  getCurrentChatId,
@@ -441,7 +485,7 @@ function useSyncChatPanelBusy(isLoading) {
441
485
  }, [isLoading, acquirePanelBusy, releasePanelBusy]);
442
486
  }
443
487
  function useChatsForScopeId(scopeId) {
444
- const { getChatsForScopeId, getCurrentChatId, setCurrentChatId, newChat, addMessage, removeMessageById, updateMessageById, sendMessage, deleteChat, } = useChats();
488
+ const { getChatsForScopeId, getCurrentChatId, setCurrentChatId, newChat, addMessage, removeMessageById, updateMessageById, updateChatMeta, setChatMessages, sendMessage, deleteChat, } = useChats();
445
489
  const chats = getChatsForScopeId(scopeId);
446
490
  const currentChatId = getCurrentChatId(scopeId);
447
491
  const currentChat = useChat(scopeId, currentChatId ?? undefined);
@@ -457,6 +501,8 @@ function useChatsForScopeId(scopeId) {
457
501
  removeMessageById: (chatId, messageId) => removeMessageById(scopeId, chatId, messageId),
458
502
  updateMessageById: (chatId, messageId, patch) => updateMessageById(scopeId, chatId, messageId, patch),
459
503
  sendMessage: (message, chatId) => sendMessage(scopeId, message, chatId),
504
+ updateChatMeta: (chatId, patch) => updateChatMeta(scopeId, chatId, patch),
505
+ setChatMessages: (chatId, messages) => setChatMessages(scopeId, chatId, messages),
460
506
  deleteChat: (sessionId) => deleteChat(scopeId, sessionId),
461
507
  };
462
508
  }
package/dist/esm/index.js CHANGED
@@ -29,6 +29,7 @@ export { ChatChrome } from './components/ui/Chat/ChatChrome/ChatChrome.js';
29
29
  export { TEXT_ATTACHMENT_ACCEPT_PARTS, filterToTextAttachments } from './components/ui/Chat/chatAttachmentAccept.js';
30
30
  export { buildChatSendMessagePayload, displayTextFromSendPayload, normalizeUserTextFileAttachments } from './components/ui/Chat/buildChatSendMessagePayload.js';
31
31
  export { sanitizeAttachmentFilename } from './components/ui/Chat/sanitizeAttachmentFilename.js';
32
+ export { ChatSelector } from './components/ui/Chat/ChatSheet/ChatSelector.js';
32
33
  export { ChatSheet } from './components/ui/Chat/ChatSheet/ChatSheet.js';
33
34
  export { useChatPanelChromeModel } from './components/ui/Chat/ChatSheet/useChatPanelChromeModel.js';
34
35
  export { ChatMessage } from './components/ui/Chat/ChatMessage/ChatMessage.js';
@@ -2,7 +2,7 @@ import { ChatPresets } from '#uilib/components/ui/Chat/ChatPresets';
2
2
  import type { ChatProps } from './Chat.types';
3
3
  import { ChatEmptyState } from './ChatEmptyState/ChatEmptyState';
4
4
  import { ChatMessage } from './ChatMessage';
5
- export declare function Chat({ children, className, isEmpty, scopeId, onChatDeleted, onNewChat, ...props }: ChatProps): import("react/jsx-runtime").JSX.Element;
5
+ export declare function Chat({ children, className, isEmpty, scopeId, onChatDeleted, onNewChat, hideChatSelector, ...props }: ChatProps): import("react/jsx-runtime").JSX.Element;
6
6
  export declare namespace Chat {
7
7
  var Prompt: import("react").ForwardRefExoticComponent<import("./Chat.types").ChatPromptProps & import("react").RefAttributes<import(".").ChatPromptComposerHandle>>;
8
8
  var Message: typeof ChatMessage;
@@ -21,7 +21,11 @@ export type ChatSendMessagePayload = {
21
21
  omitUserMessage?: boolean;
22
22
  /** Shimmer label while waiting on the API; defaults to "Thinking...". */
23
23
  loadingLabel?: string;
24
+ /** When set, show as a SYSTEM inProgress bubble instead of the footer shimmer. */
25
+ systemProgressLabel?: string;
24
26
  };
27
+ export type ChatMetaValue = string | number | boolean | null;
28
+ export type ChatMeta = Record<string, ChatMetaValue>;
25
29
  export interface Message {
26
30
  id: string;
27
31
  role: MessageRole;
@@ -30,11 +34,13 @@ export interface Message {
30
34
  userTextFileAttachments?: UserTextFileAttachment[];
31
35
  /** SYSTEM-only: transient progress placeholder while work is in flight. */
32
36
  inProgress?: boolean;
37
+ meta?: ChatMeta;
33
38
  }
34
39
  export interface Chat {
35
40
  session_id: string;
36
41
  name: string;
37
42
  messages: Message[];
43
+ meta?: ChatMeta;
38
44
  }
39
45
  export interface ChatPreset {
40
46
  id: string;
@@ -111,6 +117,10 @@ export interface ChatMessageProps {
111
117
  onScriptContinue?: () => void;
112
118
  /** Renders `[CHART]` placeholder in assistant messages (app supplies dataset chart). */
113
119
  renderMessageChart?: () => ReactNode;
120
+ /** Full message for system-role render delegation. */
121
+ message?: Message;
122
+ /** When set, SYSTEM messages render via this callback instead of plain text. */
123
+ renderSystemMessage?: (message: Message) => ReactNode;
114
124
  }
115
125
  export interface ChatProps extends HTMLAttributes<HTMLDivElement> {
116
126
  children: ReactNode;
@@ -121,4 +131,6 @@ export interface ChatProps extends HTMLAttributes<HTMLDivElement> {
121
131
  onChatDeleted?: (sessionId: string) => void;
122
132
  /** "+ New Chat" in the selector; when omitted, starts an empty session. */
123
133
  onNewChat?: () => void;
134
+ /** When true, skip built-in header ChatSelector (e.g. external page-header slot). */
135
+ hideChatSelector?: boolean;
124
136
  }
@@ -1,2 +1,2 @@
1
1
  import type { ChatChromeProps } from './ChatChrome.types';
2
- export declare function ChatChrome({ showResizeHandle, resizeHandle, onClose, isEmpty, renderPresets, messages, onQuickReply, suppressedQuickReplyKeys, isLoading, loadingLabel, scriptContinueLabel, onScriptContinue, renderMessageChart, showSyntheticBranchButtons, unusedBranchKeys, showInlinePresets, isLastMessageFromUser, scrollRef, effectiveScopeId, onPromptSubmit, onChatDeleted, onNewChat, promptPrefill, footerClassName, emptyState, allowedAttachments, allowPdfAttachments, onAttachmentsDropped, slashCommandItems, onSlashItemCommand, promptPlaceholder, }: ChatChromeProps): import("react/jsx-runtime").JSX.Element;
2
+ export declare function ChatChrome({ showResizeHandle, resizeHandle, onClose, isEmpty, renderPresets, messages, onQuickReply, suppressedQuickReplyKeys, isLoading, loadingLabel, scriptContinueLabel, onScriptContinue, renderMessageChart, renderSystemMessage, showSyntheticBranchButtons, unusedBranchKeys, showInlinePresets, isLastMessageFromUser, scrollRef, effectiveScopeId, onPromptSubmit, onChatDeleted, onNewChat, promptPrefill, footerClassName, emptyState, allowedAttachments, allowPdfAttachments, onAttachmentsDropped, slashCommandItems, onSlashItemCommand, promptPlaceholder, hideChatSelector, }: ChatChromeProps): import("react/jsx-runtime").JSX.Element;
@@ -28,6 +28,8 @@ export interface ChatChromeProps {
28
28
  scriptContinueLabel: string | undefined;
29
29
  onScriptContinue: (() => void) | undefined;
30
30
  renderMessageChart?: () => React.ReactNode;
31
+ /** When set, SYSTEM messages render via this callback instead of plain text. */
32
+ renderSystemMessage?: (message: Message) => React.ReactNode;
31
33
  showSyntheticBranchButtons: boolean;
32
34
  unusedBranchKeys: string[];
33
35
  showInlinePresets: boolean;
@@ -54,4 +56,6 @@ export interface ChatChromeProps {
54
56
  onSlashItemCommand?: SlashOnItemCommand;
55
57
  /** Composer placeholder forwarded to `Chat.Prompt`. */
56
58
  promptPlaceholder?: string;
59
+ /** When true, skip built-in header ChatSelector (e.g. external page-header slot). */
60
+ hideChatSelector?: boolean;
57
61
  }
@@ -1,2 +1,2 @@
1
1
  import { type ChatMessageProps } from '../Chat.types';
2
- export declare function ChatMessage({ role, text, inProgress, userTextFileAttachments, onQuickReply, suppressedQuickReplyKeys, quickReplyDisabled, quickReplyHidden, isLastMessage, scriptContinue, onScriptContinue, renderMessageChart, }: ChatMessageProps): import("react/jsx-runtime").JSX.Element;
2
+ export declare function ChatMessage({ role, text, inProgress, userTextFileAttachments, onQuickReply, suppressedQuickReplyKeys, quickReplyDisabled, quickReplyHidden, isLastMessage, scriptContinue, onScriptContinue, renderMessageChart, message, renderSystemMessage, }: ChatMessageProps): import("react/jsx-runtime").JSX.Element;
@@ -1,8 +1,9 @@
1
1
  export interface ChatSelectorProps {
2
2
  id: string;
3
+ wrapperClassName?: string;
3
4
  className?: string;
4
5
  onChatDeleted?: (sessionId: string) => void;
5
6
  /** When set, used for "+ New Chat" instead of the default empty `newChat()`. */
6
7
  onNewChat?: () => void;
7
8
  }
8
- export declare function ChatSelector({ id, className, onChatDeleted, onNewChat, }: ChatSelectorProps): import("react/jsx-runtime").JSX.Element;
9
+ export declare function ChatSelector({ id, wrapperClassName, className, onChatDeleted, onNewChat, }: ChatSelectorProps): import("react/jsx-runtime").JSX.Element;
@@ -21,4 +21,4 @@ export interface ChatSheetProps extends Omit<UseChatPanelChromeModelInput, 'embe
21
21
  */
22
22
  inline?: boolean;
23
23
  }
24
- export declare function ChatSheet({ triggerLabel, triggerAriaLabel, actionsRef, renderTrigger, presets, scopeId, onMessage, onScriptComplete, renderMessageChart, emptyState, allowedAttachments, allowPdfAttachments, onAttachmentsDropped, slashCommandItems, onSlashItemCommand, transformSendPayload, inline, }: ChatSheetProps): import("react/jsx-runtime").JSX.Element;
24
+ export declare function ChatSheet({ triggerLabel, triggerAriaLabel, actionsRef, renderTrigger, presets, scopeId, onMessage, onScriptComplete, renderMessageChart, renderSystemMessage, emptyState, allowedAttachments, allowPdfAttachments, onAttachmentsDropped, slashCommandItems, onSlashItemCommand, transformSendPayload, inline, }: ChatSheetProps): import("react/jsx-runtime").JSX.Element;
@@ -1,4 +1,4 @@
1
- import { ChatPreset, type ChatSendMessagePayload, type ScriptCompletePayload } from '#uilib/components/ui/Chat/Chat.types';
1
+ import { ChatPreset, type ChatSendMessagePayload, type Message, type ScriptCompletePayload } from '#uilib/components/ui/Chat/Chat.types';
2
2
  import type { SlashCommandItem, SlashOnItemCommand } from '#uilib/tiptap/slash-mention/types';
3
3
  import type { ChatChromeProps } from '../ChatChrome';
4
4
  import type { ChatAttachmentDropItem } from '../ChatChrome/ChatChrome.types';
@@ -10,11 +10,13 @@ export type UseChatPanelChromeModelInput = {
10
10
  /** Composite chat scope (e.g. `${userId}-${datasetId}`, `${userId}-dashboard`, `${userId}-report-${reportId}`). */
11
11
  scopeId?: string | null;
12
12
  /** Fires after send; second arg is the assistant reply when the API call succeeded. */
13
- onMessage?: (displayText: string, assistantResponse?: string) => void;
13
+ onMessage?: (displayText: string, assistantResponse?: string, chatSessionId?: string) => void;
14
14
  /** Fires when a preset script has no further `[Label|branchKey]` steps (graph leaf or linear script end). */
15
15
  onScriptComplete?: (payload: ScriptCompletePayload) => void;
16
16
  /** Renders `[CHART]` tokens in assistant messages. */
17
17
  renderMessageChart?: () => React.ReactNode;
18
+ /** When set, SYSTEM messages render via this callback instead of plain text. */
19
+ renderSystemMessage?: (message: Message) => React.ReactNode;
18
20
  /** Forwarded to `ChatChrome` when the thread is empty. */
19
21
  emptyState?: ChatEmptyStateConfig;
20
22
  /** MIME types / extensions for text-only chat attachments (filtered by uilib allowlist). */
@@ -44,4 +46,4 @@ export type UseChatPanelChromeModelResult = {
44
46
  openNewChatWithPrefill: (prompt: string) => void;
45
47
  chatPanelContainer: HTMLElement | null;
46
48
  };
47
- export declare function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onScriptComplete, renderMessageChart, emptyState, allowedAttachments, allowPdfAttachments, onAttachmentsDropped, slashCommandItems, onSlashItemCommand, copyHistoryOnNewChat, transformSendPayload, }: UseChatPanelChromeModelInput): UseChatPanelChromeModelResult;
49
+ export declare function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onScriptComplete, renderMessageChart, renderSystemMessage, emptyState, allowedAttachments, allowPdfAttachments, onAttachmentsDropped, slashCommandItems, onSlashItemCommand, copyHistoryOnNewChat, transformSendPayload, }: UseChatPanelChromeModelInput): UseChatPanelChromeModelResult;
@@ -5,6 +5,8 @@ export type { ChatChromeProps, ChatChromeResizeHandleConfig, } from './ChatChrom
5
5
  export { TEXT_ATTACHMENT_ACCEPT_PARTS, filterToTextAttachments, } from './chatAttachmentAccept';
6
6
  export { buildChatSendMessagePayload, displayTextFromSendPayload, normalizeUserTextFileAttachments, } from './buildChatSendMessagePayload';
7
7
  export { sanitizeAttachmentFilename } from './sanitizeAttachmentFilename';
8
+ export { ChatSelector } from './ChatSheet/ChatSelector';
9
+ export type { ChatSelectorProps } from './ChatSheet/ChatSelector';
8
10
  export { ChatSheet } from './ChatSheet/ChatSheet';
9
11
  export { useChatPanelChromeModel } from './ChatSheet/useChatPanelChromeModel';
10
12
  export type { ChatSheetActions, ChatSheetProps } from './ChatSheet/ChatSheet';
@@ -16,7 +18,7 @@ export { CHAT_PROMPT_COMMAND_CHIP_CLASS, chatPromptChipHtml, createChatPromptCom
16
18
  export type { ChatPromptComposerInsertOptions } from './ChatPrompt/ChatPromptComposer.types';
17
19
  export { ChatPresets } from './ChatPresets';
18
20
  export type { ChatEmptyStateConfig, ChatEmptyStateContext, ChatEmptyStateProps, } from './ChatEmptyState/ChatEmptyState.types';
19
- export type { Chat as ChatType, ChatAttachmentDropItem, ChatSendMessagePayload, ChatProps, ChatPreset as ChatPresetType, Message, UserTextFileAttachment, } from './Chat.types';
21
+ export type { Chat as ChatType, ChatAttachmentDropItem, ChatMeta, ChatMetaValue, ChatSendMessagePayload, ChatProps, ChatPreset as ChatPresetType, Message, UserTextFileAttachment, } from './Chat.types';
20
22
  export { MessageRole } from './Chat.types';
21
23
  export type { SlashCommandItem, SlashItemCommandContext, SlashOnItemCommand, } from '#uilib/tiptap/slash-mention/types';
22
24
  export { CsvIcon } from '../../icons/CsvIcon/CsvIcon';
@@ -1,4 +1,4 @@
1
- export type FileChipFormat = 'csv' | 'pdf' | 'text';
1
+ export type FileChipFormat = 'csv' | 'pdf' | 'text' | 'dashboard';
2
2
  export type FileChipProps = {
3
3
  name: string;
4
4
  format: FileChipFormat;