@sybilion/uilib 1.3.72 → 1.3.74

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 (39) hide show
  1. package/dist/esm/components/ui/AppHeader/AppHeader.js +2 -2
  2. package/dist/esm/components/ui/AppHeader/AppHeader.styl.js +2 -2
  3. package/dist/esm/components/ui/Chat/Chat.js +2 -2
  4. package/dist/esm/components/ui/Chat/ChatChrome/ChatChrome.js +3 -3
  5. package/dist/esm/components/ui/Chat/ChatSheet/ChatSelector.js +7 -2
  6. package/dist/esm/components/ui/Chat/ChatSheet/useChatPanelChromeModel.js +52 -12
  7. package/dist/esm/components/ui/Chat/buildChatSendMessagePayload.js +5 -1
  8. package/dist/esm/components/ui/Page/AppShell/AppShell.js +1 -1
  9. package/dist/esm/components/widgets/SybilionAppHeader/SybilionAppHeader.styl.js +1 -1
  10. package/dist/esm/contexts/chat-context.js +20 -4
  11. package/dist/esm/types/src/components/ui/AppHeader/AppHeader.d.ts +7 -1
  12. package/dist/esm/types/src/components/ui/Chat/Chat.d.ts +1 -1
  13. package/dist/esm/types/src/components/ui/Chat/Chat.types.d.ts +6 -0
  14. package/dist/esm/types/src/components/ui/Chat/ChatChrome/ChatChrome.d.ts +1 -1
  15. package/dist/esm/types/src/components/ui/Chat/ChatChrome/ChatChrome.types.d.ts +4 -0
  16. package/dist/esm/types/src/components/ui/Chat/ChatSheet/ChatSelector.d.ts +3 -1
  17. package/dist/esm/types/src/components/ui/Chat/ChatSheet/ChatSheet.d.ts +1 -1
  18. package/dist/esm/types/src/components/ui/Chat/ChatSheet/useChatPanelChromeModel.d.ts +6 -1
  19. package/dist/esm/types/src/components/ui/Chat/buildChatSendMessagePayload.d.ts +2 -0
  20. package/dist/esm/types/src/contexts/chat-context.d.ts +7 -3
  21. package/package.json +1 -1
  22. package/src/components/ui/AppHeader/AppHeader.styl +9 -0
  23. package/src/components/ui/AppHeader/AppHeader.styl.d.ts +1 -0
  24. package/src/components/ui/AppHeader/AppHeader.tsx +13 -1
  25. package/src/components/ui/Chat/Chat.tsx +6 -1
  26. package/src/components/ui/Chat/Chat.types.ts +6 -0
  27. package/src/components/ui/Chat/ChatChrome/ChatChrome.tsx +5 -2
  28. package/src/components/ui/Chat/ChatChrome/ChatChrome.types.ts +4 -0
  29. package/src/components/ui/Chat/ChatSheet/ChatSelector.tsx +8 -1
  30. package/src/components/ui/Chat/ChatSheet/ChatSheet.tsx +1 -1
  31. package/src/components/ui/Chat/ChatSheet/useChatPanelChromeModel.tsx +62 -10
  32. package/src/components/ui/Chat/buildChatSendMessagePayload.ts +7 -0
  33. package/src/components/ui/Page/AppShell/AppShell.tsx +8 -1
  34. package/src/components/widgets/SybilionAppHeader/SybilionAppHeader.styl +15 -3
  35. package/src/contexts/chat-context.tsx +30 -5
  36. package/src/docs/DocsShell.tsx +13 -1
  37. package/src/docs/pages/StandaloneAppLayoutPage/StandaloneAppLayoutPage.styl +3 -0
  38. package/src/docs/pages/StandaloneAppLayoutPage/StandaloneAppLayoutPage.tsx +54 -54
  39. package/src/docs/registry.ts +0 -6
@@ -5,8 +5,8 @@ import { createPortal } from 'react-dom';
5
5
  import S from './AppHeader.styl.js';
6
6
  import { PAGE_HEADER_ID } from './appChromeAnchors.js';
7
7
 
8
- function AppHeaderHost({ className, anchorId = PAGE_HEADER_ID, }) {
9
- return jsx("header", { className: cn(S.root, className), id: anchorId });
8
+ function AppHeaderHost({ className, anchorId = PAGE_HEADER_ID, sticky, }) {
9
+ return (jsx("header", { className: cn(S.root, sticky && S.sticky, className), id: anchorId }));
10
10
  }
11
11
  function AppHeaderPortal({ children, pageHeaderId = PAGE_HEADER_ID, }) {
12
12
  const [container, setContainer] = useState(null);
@@ -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)}}.AppHeader_root__SdbDv{align-items:center;align-self:flex-end;background-color:var(--color-background);display:flex;max-width:calc(100vw - var(--sidebar-width) - var(--p-3));min-height:var(--header-height);padding-right:var(--p-2);width:100%}@media (max-width:768px){.AppHeader_root__SdbDv{max-width:100%;padding-left:200px}}@media (min-width:768px){[data-slot=sidebar-wrapper][data-state=collapsed] .AppHeader_root__SdbDv{max-width:100%;padding-left:200px}}.AppHeader_content__kyxem{align-items:center;display:flex;gap:2rem;justify-content:flex-end;padding:0 var(--p-9);width:100%}.AppHeader_logo__v31FX{align-items:center;color:var(--color-foreground);display:flex;font-size:1.5rem;font-weight:400;gap:.5rem;text-decoration:none;white-space:nowrap}.AppHeader_logo__v31FX svg{display:inline-flex;height:32px;transition:transform .1s ease-in-out;width:auto}.AppHeader_logo__v31FX:hover svg{transform:scale(1.05)}.AppHeader_nav__ahN1p{align-items:center;display:flex;flex:1;gap:1.5rem;justify-content:center}@media (max-width:units(768px,\"px\")){.AppHeader_nav__ahN1p{display:none}}.AppHeader_navLink__z43jY{color:var(--color-muted-foreground);font-size:.875rem;font-weight:400;text-decoration:none;transition:color .2s ease}.AppHeader_navLink__z43jY:hover{color:var(--color-foreground)}.AppHeader_actions__QuxEF{align-items:center;display:flex;gap:var(--p-4);margin-right:var(--p-2)}@media (max-width:units(768px,\"px\")){.AppHeader_actions__QuxEF{gap:var(--p-1)}}.AppHeader_pageHeaderActionsAnchor__qzQMs{align-items:center;display:flex;flex-shrink:0;gap:var(--p-4)}";
4
- var S = {"root":"AppHeader_root__SdbDv","content":"AppHeader_content__kyxem","logo":"AppHeader_logo__v31FX","nav":"AppHeader_nav__ahN1p","navLink":"AppHeader_navLink__z43jY","actions":"AppHeader_actions__QuxEF","pageHeaderActionsAnchor":"AppHeader_pageHeaderActionsAnchor__qzQMs"};
3
+ var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.AppHeader_root__SdbDv{align-items:center;align-self:flex-end;background-color:var(--color-background);display:flex;max-width:calc(100vw - var(--sidebar-width) - var(--p-3));min-height:var(--header-height);padding-right:var(--p-2);position:relative;width:100%}@media (max-width:768px){.AppHeader_root__SdbDv{max-width:100%;padding-left:200px}}@media (min-width:768px){[data-slot=sidebar-wrapper][data-state=collapsed] .AppHeader_root__SdbDv{max-width:100%;padding-left:200px}}.AppHeader_sticky__qFWuq{backdrop-filter:blur(10px);background-color:color-mix(in srgb,var(--background) 80%,transparent);box-shadow:inset 0 40px 50px 20px var(--background);position:sticky;top:0;z-index:100}.AppHeader_content__kyxem{align-items:center;display:flex;gap:2rem;justify-content:flex-end;padding:0 var(--p-9);width:100%}.AppHeader_logo__v31FX{align-items:center;color:var(--color-foreground);display:flex;font-size:1.5rem;font-weight:400;gap:.5rem;text-decoration:none;white-space:nowrap}.AppHeader_logo__v31FX svg{display:inline-flex;height:32px;transition:transform .1s ease-in-out;width:auto}.AppHeader_logo__v31FX:hover svg{transform:scale(1.05)}.AppHeader_nav__ahN1p{align-items:center;display:flex;flex:1;gap:1.5rem;justify-content:center}@media (max-width:units(768px,\"px\")){.AppHeader_nav__ahN1p{display:none}}.AppHeader_navLink__z43jY{color:var(--color-muted-foreground);font-size:.875rem;font-weight:400;text-decoration:none;transition:color .2s ease}.AppHeader_navLink__z43jY:hover{color:var(--color-foreground)}.AppHeader_actions__QuxEF{align-items:center;display:flex;gap:var(--p-4);margin-right:var(--p-2)}@media (max-width:units(768px,\"px\")){.AppHeader_actions__QuxEF{gap:var(--p-1)}}.AppHeader_pageHeaderActionsAnchor__qzQMs{align-items:center;display:flex;flex-shrink:0;gap:var(--p-4)}";
4
+ var S = {"root":"AppHeader_root__SdbDv","sticky":"AppHeader_sticky__qFWuq","content":"AppHeader_content__kyxem","logo":"AppHeader_logo__v31FX","nav":"AppHeader_nav__ahN1p","navLink":"AppHeader_navLink__z43jY","actions":"AppHeader_actions__QuxEF","pageHeaderActionsAnchor":"AppHeader_pageHeaderActionsAnchor__qzQMs"};
5
5
  styleInject(css_248z);
6
6
 
7
7
  export { S as default };
@@ -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, ...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 }) })) : null, children] }));
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] }));
12
12
  }
13
13
  Chat.Prompt = ChatPrompt;
14
14
  Chat.Message = ChatMessage;
@@ -14,7 +14,7 @@ import { filterToTextAttachments, isAttachmentsDropzoneEnabled, buildAcceptAttr
14
14
  import { extractChatAttachmentItems } from '../chatAttachmentExtract.js';
15
15
  import S from './ChatChrome.styl.js';
16
16
 
17
- function ChatChrome({ showResizeHandle, resizeHandle, onClose, isEmpty, renderPresets, messages, onQuickReply, suppressedQuickReplyKeys, isLoading, scriptContinueLabel, onScriptContinue, renderMessageChart, showSyntheticBranchButtons, unusedBranchKeys, showInlinePresets, isLastMessageFromUser, scrollRef, effectiveScopeId, onPromptSubmit, onChatDeleted, promptPrefill, footerClassName, emptyState, allowedAttachments, allowPdfAttachments = false, onAttachmentsDropped, slashCommandItems, onSlashItemCommand, promptPlaceholder, }) {
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
18
  const filteredAllowedAttachments = useMemo(() => filterToTextAttachments(allowedAttachments), [allowedAttachments]);
19
19
  const attachmentsDropzoneEnabled = isAttachmentsDropzoneEnabled(allowedAttachments, allowPdfAttachments);
20
20
  const attachmentAccept = useMemo(() => buildAcceptAttr(filteredAllowedAttachments, allowPdfAttachments), [filteredAllowedAttachments, allowPdfAttachments]);
@@ -66,7 +66,7 @@ function ChatChrome({ showResizeHandle, resizeHandle, onClose, isEmpty, renderPr
66
66
  if (inner)
67
67
  setTimeout(scrollToBottom, 100);
68
68
  }, [isEmpty, messages.length]);
69
- 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, 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) => {
69
+ 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) => {
70
70
  const isLast = index === arr.length - 1;
71
71
  return (jsx(Chat.Message, { role: msg.role, text: msg.text, inProgress: msg.inProgress, userTextFileAttachments: msg.userTextFileAttachments, onQuickReply: onQuickReply, suppressedQuickReplyKeys: suppressedQuickReplyKeys, quickReplyDisabled: isLoading, isLastMessage: isLast, scriptContinue: isLast && scriptContinueLabel
72
72
  ? { label: scriptContinueLabel }
@@ -77,7 +77,7 @@ function ChatChrome({ showResizeHandle, resizeHandle, onClose, isEmpty, renderPr
77
77
  const label = displayLabelForBranchKeyFromMessages(key, messages) ??
78
78
  humanizeBranchKey(key);
79
79
  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));
80
- }) })) : null, showInlinePresets && renderPresets('inline'), isLoading && isLastMessageFromUser && (jsx(TextShimmer, { duration: 1, spread: 5, className: S.loader, children: "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 })] })] })] })] }));
80
+ }) })) : 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 })] })] })] })] }));
81
81
  }
82
82
 
83
83
  export { ChatChrome };
@@ -6,11 +6,16 @@ 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, }) {
9
+ function ChatSelector({ id, className, onChatDeleted, onNewChat, }) {
10
10
  const { chats, currentChatId, setCurrentChatId, newChat, deleteChat } = useChatsForScopeId(id);
11
11
  const handleValueChange = (value) => {
12
12
  if (value === 'new') {
13
- newChat();
13
+ if (onNewChat) {
14
+ onNewChat();
15
+ }
16
+ else {
17
+ newChat();
18
+ }
14
19
  }
15
20
  else if (value) {
16
21
  setCurrentChatId(value);
@@ -2,7 +2,7 @@ import { jsx } from 'react/jsx-runtime';
2
2
  import { useState, useRef, useEffect, useMemo, useCallback } from 'react';
3
3
  import { MessageRole } from '../Chat.types.js';
4
4
  import { isGraphIntakeAssistantStepComplete, matchUserTextToQuickReply, isPresetScriptGraph, branchesFromPresetScriptGraph, parseScriptLine, textHasQuickReplyMarkers, branchKeysUsedFromChatHistory, branchKeysUsedByUserMessages, extractQuickReplyLabelKeyPairsFromText, entryBranchKeyBeforeLastAssistant } from '../ChatMessage/presetScript.js';
5
- import { displayTextFromSendPayload, buildChatSendMessagePayload } from '../buildChatSendMessagePayload.js';
5
+ import { displayTextFromSendPayload, buildChatSendMessagePayload, loadingLabelFromSendPayload } from '../buildChatSendMessagePayload.js';
6
6
  import { usedPresetIdsFromMessages } from '../chat-preset-utils.js';
7
7
  import { useChatsForScopeId, useChat, useChatOutboundPending, useSyncChatPanelBusy, isChatEmpty } from '../../../../contexts/chat-context.js';
8
8
  import { shellFitsSidebarsLayout } from '../../../../hooks/panelWidth.js';
@@ -18,15 +18,16 @@ import { shouldOpenChatFromUrl, shouldHealChatShellDesync, shouldCloseStaleLocal
18
18
  /** Fallback when `scopeId` prop omitted; apps should pass an explicit composite scope (e.g. `${userId}-dashboard`). */
19
19
  const NO_SCOPE_FALLBACK = '__uilib_chat_scope_unset__';
20
20
  const SCRIPT_STEP_DELAY_MS = 1200;
21
- const CHAT_NEW_SHORTCUT_KEY = 'o';
21
+ const CHAT_NEW_SHORTCUT_KEY = '0';
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, transformSendPayload, }) {
25
+ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onScriptComplete, renderMessageChart, 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();
29
29
  const [localUiBusy, setLocalUiBusy] = useState(false);
30
+ const [outboundLoadingLabel, setOutboundLoadingLabel] = useState();
30
31
  const { chats, currentChatId, setCurrentChatId, newChat, sendMessage, addMessage, removeMessageById, } = useChatsForScopeId(effectiveScopeId);
31
32
  const chat = useChat(effectiveScopeId, currentChatId);
32
33
  const isOutboundPending = useChatOutboundPending(effectiveScopeId, currentChatId);
@@ -158,8 +159,39 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
158
159
  }
159
160
  };
160
161
  const isEmpty = isChatEmpty(chat) && !isLoading;
161
- const openNewChatWithPrefill = useCallback((prompt) => {
162
+ const startEmptyNewChat = useCallback(() => {
163
+ const priorChatId = currentChatId;
162
164
  const sessionId = newChat();
165
+ if (sessionId && priorChatId) {
166
+ endLocalDemoFlow(priorChatId);
167
+ }
168
+ if (sessionId) {
169
+ setPromptLinkPrefill(null);
170
+ }
171
+ return sessionId;
172
+ }, [newChat, currentChatId, endLocalDemoFlow]);
173
+ const startNewChatFromSelector = useCallback(() => {
174
+ const priorChatId = currentChatId;
175
+ const messages = chat?.messages ?? [];
176
+ const sessionId = newChat(copyHistoryOnNewChat && messages.length > 0
177
+ ? { seedMessages: messages }
178
+ : undefined);
179
+ if (sessionId && priorChatId) {
180
+ endLocalDemoFlow(priorChatId);
181
+ }
182
+ if (sessionId) {
183
+ setPromptLinkPrefill(null);
184
+ }
185
+ return sessionId;
186
+ }, [
187
+ newChat,
188
+ copyHistoryOnNewChat,
189
+ chat?.messages,
190
+ currentChatId,
191
+ endLocalDemoFlow,
192
+ ]);
193
+ const openNewChatWithPrefill = useCallback((prompt) => {
194
+ const sessionId = startEmptyNewChat();
163
195
  if (sessionId == null) {
164
196
  logger.warn('Chat prefill: sign in to use the assistant.');
165
197
  return;
@@ -167,7 +199,7 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
167
199
  const trimmed = prompt.trim();
168
200
  setPromptLinkPrefill(trimmed || null);
169
201
  onOpenChange(true);
170
- }, [newChat, onOpenChange]);
202
+ }, [startEmptyNewChat, onOpenChange]);
171
203
  /**
172
204
  * App link: `?prompt=…` — open panel, pre-fill composer, strip param (read once on mount
173
205
  * from `location.search`). If the selected session already has messages, `newChat()` first.
@@ -193,7 +225,7 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
193
225
  promptParamHandledInEffectRef.current = true;
194
226
  const needsFirstSession = chats.length === 0;
195
227
  if (!isEmpty || needsFirstSession) {
196
- const sessionId = newChat();
228
+ const sessionId = startEmptyNewChat();
197
229
  if (sessionId == null) {
198
230
  logger.warn('Chat prompt link: sign in to use the assistant.');
199
231
  mutateSearchParams(n => {
@@ -216,14 +248,14 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
216
248
  event: 'keydown',
217
249
  callback: (e) => {
218
250
  const ke = e;
219
- if (ke.key.toLowerCase() !== CHAT_NEW_SHORTCUT_KEY ||
251
+ if (ke.key !== CHAT_NEW_SHORTCUT_KEY ||
220
252
  !(ke.metaKey || ke.ctrlKey) ||
221
- !ke.shiftKey ||
253
+ ke.shiftKey ||
222
254
  ke.altKey) {
223
255
  return;
224
256
  }
225
257
  ke.preventDefault();
226
- newChat();
258
+ startEmptyNewChat();
227
259
  onOpenChange(true);
228
260
  },
229
261
  });
@@ -433,8 +465,14 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
433
465
  if (transformSendPayload) {
434
466
  payload = await transformSendPayload(message, stagedAttachments, payload);
435
467
  }
436
- const assistantResponse = await sendMessage(payload);
437
- onMessage?.(displayTextFromSendPayload(payload), assistantResponse);
468
+ setOutboundLoadingLabel(loadingLabelFromSendPayload(payload));
469
+ try {
470
+ const assistantResponse = await sendMessage(payload);
471
+ onMessage?.(displayTextFromSendPayload(payload), assistantResponse);
472
+ }
473
+ finally {
474
+ setOutboundLoadingLabel(undefined);
475
+ }
438
476
  }
439
477
  catch (error) {
440
478
  logger.error('Error sending chat message:', error);
@@ -864,6 +902,7 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
864
902
  onQuickReply,
865
903
  suppressedQuickReplyKeys,
866
904
  isLoading,
905
+ loadingLabel: outboundLoadingLabel,
867
906
  scriptContinueLabel,
868
907
  onScriptContinue,
869
908
  renderMessageChart,
@@ -875,6 +914,7 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
875
914
  effectiveScopeId,
876
915
  onPromptSubmit: onPromptSubmitWithSlashCommands,
877
916
  onChatDeleted: endLocalDemoFlow,
917
+ onNewChat: startNewChatFromSelector,
878
918
  promptPrefill: promptLinkPrefill,
879
919
  emptyState: resolvedEmptyState,
880
920
  allowedAttachments,
@@ -895,7 +935,7 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
895
935
  isOpen,
896
936
  onOpenChange,
897
937
  toggleOpen,
898
- newChat,
938
+ newChat: startEmptyNewChat,
899
939
  openNewChatWithPrefill,
900
940
  chatPanelContainer,
901
941
  };
@@ -66,5 +66,9 @@ function buildChatSendMessagePayload(displayText, attachments) {
66
66
  function displayTextFromSendPayload(message) {
67
67
  return typeof message === 'string' ? message : message.displayText;
68
68
  }
69
+ /** Optional loading shimmer label from a structured send payload. */
70
+ function loadingLabelFromSendPayload(message) {
71
+ return typeof message === 'string' ? undefined : message.loadingLabel;
72
+ }
69
73
 
70
- export { buildChatSendMessagePayload, displayTextFromSendPayload, normalizeUserTextFileAttachments };
74
+ export { buildChatSendMessagePayload, displayTextFromSendPayload, loadingLabelFromSendPayload, normalizeUserTextFileAttachments };
@@ -8,7 +8,7 @@ function AppShellMainContent({ className, bodyClassName, children, header, foote
8
8
  return (jsxs("div", { className: cn(S.mainColumn, className), children: [header, jsx("div", { className: cn(S.mainBody, bodyClassName), children: children }), footer] }));
9
9
  }
10
10
  const AppShell = forwardRef(function AppShell({ className, ...props }, ref) {
11
- return jsx("div", { ref: ref, className: cn(S.root, className), ...props });
11
+ return (jsx("div", { ref: ref, "data-slot": "app-shell", className: cn(S.root, className), ...props }));
12
12
  });
13
13
 
14
14
  export { AppShell, AppShellMainContent };
@@ -1,6 +1,6 @@
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)}}.SybilionAppHeader_actionsAnchor__ress2,.SybilionAppHeader_pageActionsPortal__9C5ww{align-items:center;display:flex;flex-shrink:0;gap:var(--p-4)}.SybilionAppHeader_pageActionsPortal__9C5ww:empty{display:none}.SybilionAppHeader_logoArea__3HAhG{align-items:center;display:flex;gap:var(--p-2);left:40px;position:absolute;top:22px;z-index:10}@media (max-width:768px){.SybilionAppHeader_logoArea__3HAhG{left:32px}}@media (min-width:768px){[data-slot=sidebar-wrapper][data-state=expanded] .SybilionAppHeader_logoArea__3HAhG{position:fixed}}.SybilionAppHeader_logoAreaWithBanner__7Iy78{top:22px;top:calc(22px + var(--welcome-banner-height, 0px))}@media (min-width:768px){[data-slot=sidebar-wrapper][data-state=collapsed] .SybilionAppHeader_logoAreaWithBanner__7Iy78{top:22px}}.SybilionAppHeader_logoLink__bH-KX{align-items:center;color:var(--color-foreground);display:flex;font-size:1.5rem;font-weight:400;gap:.5rem;text-decoration:none;white-space:nowrap;width:-moz-fit-content;width:fit-content}.SybilionAppHeader_logoLink__bH-KX svg{display:inline-flex;flex-shrink:0;height:32px;transition:transform .1s ease-in-out;width:auto}.SybilionAppHeader_logoLink__bH-KX:hover svg{transform:scale(1.05)}";
3
+ var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.SybilionAppHeader_actionsAnchor__ress2,.SybilionAppHeader_pageActionsPortal__9C5ww{align-items:center;display:flex;flex-shrink:0;gap:var(--p-4)}.SybilionAppHeader_pageActionsPortal__9C5ww:empty{display:none}.SybilionAppHeader_logoArea__3HAhG{align-items:center;display:flex;gap:var(--p-2);left:40px;position:absolute;top:22px;z-index:10}@media (max-width:768px){.SybilionAppHeader_logoArea__3HAhG{left:32px}}@media (min-width:768px){[data-slot=sidebar-wrapper][data-state=expanded]:not([data-slot=app-shell] [data-slot=sidebar-wrapper]) [data-slot=app-shell]:not([data-slot=app-shell] [data-slot=app-shell]) .SybilionAppHeader_logoArea__3HAhG:not([data-slot=app-shell] [data-slot=app-shell] *){left:calc(var(--sidebar-width)*-1 + 52px);position:absolute}[data-slot=app-shell] [data-slot=sidebar-wrapper][data-state=expanded] [data-slot=app-shell] .SybilionAppHeader_logoArea__3HAhG{left:calc(var(--sidebar-width)*-1 + 44px);position:absolute}[data-slot=app-shell] [data-slot=sidebar-wrapper][data-state=collapsed] [data-slot=app-shell] .SybilionAppHeader_logoArea__3HAhG{left:var(--p-8);position:absolute}}.SybilionAppHeader_logoAreaWithBanner__7Iy78{top:22px;top:calc(22px + var(--welcome-banner-height, 0px))}@media (min-width:768px){[data-slot=sidebar-wrapper][data-state=collapsed]:not([data-slot=app-shell] [data-slot=sidebar-wrapper]) [data-slot=app-shell]:not([data-slot=app-shell] [data-slot=app-shell]) .SybilionAppHeader_logoAreaWithBanner__7Iy78{top:22px}}.SybilionAppHeader_logoLink__bH-KX{align-items:center;color:var(--color-foreground);display:flex;font-size:1.5rem;font-weight:400;gap:.5rem;text-decoration:none;white-space:nowrap;width:-moz-fit-content;width:fit-content}.SybilionAppHeader_logoLink__bH-KX svg{display:inline-flex;flex-shrink:0;height:32px;transition:transform .1s ease-in-out;width:auto}.SybilionAppHeader_logoLink__bH-KX:hover svg{transform:scale(1.05)}";
4
4
  var S = {"actionsAnchor":"SybilionAppHeader_actionsAnchor__ress2","pageActionsPortal":"SybilionAppHeader_pageActionsPortal__9C5ww","logoArea":"SybilionAppHeader_logoArea__3HAhG","logoAreaWithBanner":"SybilionAppHeader_logoAreaWithBanner__7Iy78","logoLink":"SybilionAppHeader_logoLink__bH-KX"};
5
5
  styleInject(css_248z);
6
6
 
@@ -80,6 +80,19 @@ function addScopeIdToRegistry(scopeId) {
80
80
  LS.set(CHAT_SCOPE_IDS_REGISTRY_KEY, [...registry, scopeId]);
81
81
  }
82
82
  }
83
+ /** Shallow-clone messages for seeding another session; drops in-progress rows. */
84
+ function cloneMessagesForNewSession(messages) {
85
+ return messages
86
+ .filter(message => !message.inProgress)
87
+ .map(message => ({
88
+ ...message,
89
+ ...(message.userTextFileAttachments
90
+ ? {
91
+ userTextFileAttachments: message.userTextFileAttachments.map(attachment => ({ ...attachment })),
92
+ }
93
+ : {}),
94
+ }));
95
+ }
83
96
  function ChatProvider({ children, userSwitchKey, sendChatMessage: sendChatMessageFn, }) {
84
97
  const [chats, setChats] = useState(() => {
85
98
  if (userSwitchKey === null)
@@ -125,15 +138,18 @@ function ChatProvider({ children, userSwitchKey, sendChatMessage: sendChatMessag
125
138
  return null;
126
139
  return v;
127
140
  }, [currentChatId]);
128
- const newChat = useCallback((scopeId) => {
141
+ const newChat = useCallback((scopeId, options) => {
129
142
  if (userSwitchKey === null)
130
143
  return undefined;
131
144
  addScopeIdToRegistry(scopeId);
132
145
  const sessionId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
146
+ const seededMessages = options?.seedMessages && options.seedMessages.length > 0
147
+ ? cloneMessagesForNewSession(options.seedMessages)
148
+ : [];
133
149
  const newChat = {
134
150
  session_id: sessionId,
135
151
  name: '',
136
- messages: [],
152
+ messages: seededMessages,
137
153
  };
138
154
  setChats(prev => {
139
155
  const currentChats = prev[scopeId] ?? [];
@@ -296,7 +312,7 @@ function ChatProvider({ children, userSwitchKey, sendChatMessage: sendChatMessag
296
312
  if (typeof message === 'string') {
297
313
  addMessage(scopeId, targetChatId, MessageRole.USER, message);
298
314
  }
299
- else {
315
+ else if (!message.omitUserMessage) {
300
316
  addMessage(scopeId, targetChatId, MessageRole.USER, message.displayText, {
301
317
  userTextFileAttachments: normalizeUserTextFileAttachments(message),
302
318
  });
@@ -436,7 +452,7 @@ function useChatsForScopeId(scopeId) {
436
452
  currentChatId,
437
453
  isOutboundPending,
438
454
  setCurrentChatId: (targetId) => setCurrentChatId(scopeId, targetId),
439
- newChat: () => newChat(scopeId),
455
+ newChat: (options) => newChat(scopeId, options),
440
456
  addMessage: (chatId, role, text, options) => addMessage(scopeId, chatId, role, text, options),
441
457
  removeMessageById: (chatId, messageId) => removeMessageById(scopeId, chatId, messageId),
442
458
  updateMessageById: (chatId, messageId, patch) => updateMessageById(scopeId, chatId, messageId, patch),
@@ -3,8 +3,14 @@ export type AppHeaderProps = {
3
3
  className?: string;
4
4
  /** Override default anchor id when multiple headers exist (e.g. docs demos). */
5
5
  anchorId?: string;
6
+ /**
7
+ * Pins the host to the top of its scroll container (`position: sticky; top: 0`).
8
+ * Use with `PageScroll` or any ancestor that scrolls; portaled header content
9
+ * from `AppHeaderPortal` / `SybilionAppHeader` stays inside this element.
10
+ */
11
+ sticky?: boolean;
6
12
  };
7
- export declare function AppHeaderHost({ className, anchorId, }: AppHeaderProps): import("react/jsx-runtime").JSX.Element;
13
+ export declare function AppHeaderHost({ className, anchorId, sticky, }: AppHeaderProps): import("react/jsx-runtime").JSX.Element;
8
14
  export type AppHeaderPortalProps = {
9
15
  children: ReactNode;
10
16
  pageHeaderId?: string;
@@ -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, ...props }: ChatProps): import("react/jsx-runtime").JSX.Element;
5
+ export declare function Chat({ children, className, isEmpty, scopeId, onChatDeleted, onNewChat, ...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;
@@ -17,6 +17,10 @@ export type ChatSendMessagePayload = {
17
17
  apiMessage: string;
18
18
  displayText: string;
19
19
  userTextFileAttachments?: UserTextFileAttachment[];
20
+ /** When true, skip adding a USER bubble before the API call (e.g. slash-command actions). */
21
+ omitUserMessage?: boolean;
22
+ /** Shimmer label while waiting on the API; defaults to "Thinking...". */
23
+ loadingLabel?: string;
20
24
  };
21
25
  export interface Message {
22
26
  id: string;
@@ -113,4 +117,6 @@ export interface ChatProps extends HTMLAttributes<HTMLDivElement> {
113
117
  /** Chat context scope; when set, header shows chat selector + delete. */
114
118
  scopeId?: string;
115
119
  onChatDeleted?: (sessionId: string) => void;
120
+ /** "+ New Chat" in the selector; when omitted, starts an empty session. */
121
+ onNewChat?: () => void;
116
122
  }
@@ -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, scriptContinueLabel, onScriptContinue, renderMessageChart, showSyntheticBranchButtons, unusedBranchKeys, showInlinePresets, isLastMessageFromUser, scrollRef, effectiveScopeId, onPromptSubmit, onChatDeleted, 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, 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;
@@ -23,6 +23,8 @@ export interface ChatChromeProps {
23
23
  onQuickReply: (branchKey: string, displayLabel: string) => void;
24
24
  suppressedQuickReplyKeys: ReadonlySet<string>;
25
25
  isLoading: boolean;
26
+ /** Overrides default "Thinking..." shimmer while waiting on the API. */
27
+ loadingLabel?: string;
26
28
  scriptContinueLabel: string | undefined;
27
29
  onScriptContinue: (() => void) | undefined;
28
30
  renderMessageChart?: () => React.ReactNode;
@@ -34,6 +36,8 @@ export interface ChatChromeProps {
34
36
  effectiveScopeId: string;
35
37
  onPromptSubmit: (message: string, attachments?: ChatAttachmentDropItem[]) => void | Promise<void>;
36
38
  onChatDeleted: (sessionId: string) => void;
39
+ /** "+ New Chat" in the header selector; omit for default empty session. */
40
+ onNewChat?: () => void;
37
41
  /** `?prompt=` deep link text for one-shot composer pre-fill. */
38
42
  promptPrefill?: string | null;
39
43
  footerClassName?: string;
@@ -2,5 +2,7 @@ export interface ChatSelectorProps {
2
2
  id: string;
3
3
  className?: string;
4
4
  onChatDeleted?: (sessionId: string) => void;
5
+ /** When set, used for "+ New Chat" instead of the default empty `newChat()`. */
6
+ onNewChat?: () => void;
5
7
  }
6
- export declare function ChatSelector({ id, className, onChatDeleted, }: ChatSelectorProps): import("react/jsx-runtime").JSX.Element;
8
+ export declare function ChatSelector({ id, className, onChatDeleted, onNewChat, }: ChatSelectorProps): import("react/jsx-runtime").JSX.Element;
@@ -1,7 +1,7 @@
1
1
  import { type UseChatPanelChromeModelInput } from './useChatPanelChromeModel';
2
2
  export type ChatSheetActions = {
3
3
  open: () => void;
4
- /** Starts a new chat session and opens the panel (same as the new-chat keyboard shortcut). */
4
+ /** Starts an empty chat session and opens the panel (same as Cmd/Ctrl+0). */
5
5
  openNewChat: () => void;
6
6
  /** Starts a new chat, opens the panel, and pre-fills the composer (user sends manually). */
7
7
  openNewChatWithPrefill: (prompt: string) => void;
@@ -26,6 +26,11 @@ export type UseChatPanelChromeModelInput = {
26
26
  slashCommandItems?: SlashCommandItem[];
27
27
  /** Custom slash command handler (palette pick or Enter on `/id`). */
28
28
  onSlashItemCommand?: SlashOnItemCommand;
29
+ /**
30
+ * When true, "+ New Chat" in the selector seeds from the current session.
31
+ * Keyboard shortcut and other new-chat entry points always start empty.
32
+ */
33
+ copyHistoryOnNewChat?: boolean;
29
34
  /** Override or extend the default send payload (e.g. api vs display text split). */
30
35
  transformSendPayload?: (message: string, attachments: ChatAttachmentDropItem[] | undefined, defaultPayload: string | ChatSendMessagePayload) => string | ChatSendMessagePayload | Promise<string | ChatSendMessagePayload>;
31
36
  };
@@ -39,4 +44,4 @@ export type UseChatPanelChromeModelResult = {
39
44
  openNewChatWithPrefill: (prompt: string) => void;
40
45
  chatPanelContainer: HTMLElement | null;
41
46
  };
42
- export declare function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onScriptComplete, renderMessageChart, emptyState, allowedAttachments, allowPdfAttachments, onAttachmentsDropped, slashCommandItems, onSlashItemCommand, transformSendPayload, }: UseChatPanelChromeModelInput): UseChatPanelChromeModelResult;
47
+ export declare function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onScriptComplete, renderMessageChart, emptyState, allowedAttachments, allowPdfAttachments, onAttachmentsDropped, slashCommandItems, onSlashItemCommand, copyHistoryOnNewChat, transformSendPayload, }: UseChatPanelChromeModelInput): UseChatPanelChromeModelResult;
@@ -8,3 +8,5 @@ export declare function normalizeUserTextFileAttachments(payload: ChatSendMessag
8
8
  export declare function buildChatSendMessagePayload(displayText: string, attachments: readonly ChatAttachmentDropItem[]): string | ChatSendMessagePayload;
9
9
  /** Display text from a string or structured send payload. */
10
10
  export declare function displayTextFromSendPayload(message: string | ChatSendMessagePayload): string;
11
+ /** Optional loading shimmer label from a structured send payload. */
12
+ export declare function loadingLabelFromSendPayload(message: string | ChatSendMessagePayload): string | undefined;
@@ -13,9 +13,13 @@ export type UpdateChatMessagePatch = {
13
13
  text?: string;
14
14
  inProgress?: boolean;
15
15
  };
16
+ export type NewChatOptions = {
17
+ /** When set, seeds the new session's messages (e.g. continue dialog in reports). */
18
+ seedMessages?: readonly Message[];
19
+ };
16
20
  export interface ChatContextType {
17
21
  /** Returns the new session id, or undefined if no user / not created. */
18
- newChat: (scopeId: string) => string | undefined;
22
+ newChat: (scopeId: string, options?: NewChatOptions) => string | undefined;
19
23
  setCurrentChatId: (currScopeId: string, sessionId: string) => void;
20
24
  addMessage: (scopeId: string, chatId: string, role: MessageRole, text: string, options?: AddChatMessageOptions) => string | undefined;
21
25
  removeMessageById: (scopeId: string, chatId: string, messageId: string) => void;
@@ -60,7 +64,7 @@ export declare function useChatsForScopeId(scopeId: string): {
60
64
  currentChatId: string;
61
65
  isOutboundPending: boolean;
62
66
  setCurrentChatId: (targetId: string) => void;
63
- newChat: () => string;
67
+ newChat: (options?: NewChatOptions) => string;
64
68
  addMessage: (chatId: string, role: MessageRole, text: string, options?: AddChatMessageOptions) => string;
65
69
  removeMessageById: (chatId: string, messageId: string) => void;
66
70
  updateMessageById: (chatId: string, messageId: string, patch: UpdateChatMessagePatch) => void;
@@ -74,7 +78,7 @@ export declare function useChatsForDataset(scopeId: string): {
74
78
  currentChatId: string;
75
79
  isOutboundPending: boolean;
76
80
  setCurrentChatId: (targetId: string) => void;
77
- newChat: () => string;
81
+ newChat: (options?: NewChatOptions) => string;
78
82
  addMessage: (chatId: string, role: MessageRole, text: string, options?: AddChatMessageOptions) => string;
79
83
  removeMessageById: (chatId: string, messageId: string) => void;
80
84
  updateMessageById: (chatId: string, messageId: string, patch: UpdateChatMessagePatch) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sybilion/uilib",
3
- "version": "1.3.72",
3
+ "version": "1.3.74",
4
4
  "description": "Sybilion Design System — React UI components (Webpack + Stylus)",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -1,6 +1,7 @@
1
1
  @import '../../../lib/theme.styl'
2
2
 
3
3
  .root
4
+ position relative
4
5
  display flex
5
6
  align-items center
6
7
  align-self flex-end
@@ -20,6 +21,14 @@
20
21
  padding-left 200px
21
22
  max-width 100%
22
23
 
24
+ .sticky
25
+ z-index 100
26
+ position sticky
27
+ top 0
28
+ background-color unquote('color-mix(in srgb, var(--background) 80%, transparent)')
29
+ backdrop-filter blur(10px)
30
+ box-shadow inset 0 40px 50px 20px var(--background)
31
+
23
32
  .content
24
33
  display flex
25
34
  align-items center
@@ -8,6 +8,7 @@ interface CssExports {
8
8
  'navLink': string;
9
9
  'pageHeaderActionsAnchor': string;
10
10
  'root': string;
11
+ 'sticky': string;
11
12
  }
12
13
  export const cssExports: CssExports;
13
14
  export default cssExports;
@@ -9,13 +9,25 @@ export type AppHeaderProps = {
9
9
  className?: string;
10
10
  /** Override default anchor id when multiple headers exist (e.g. docs demos). */
11
11
  anchorId?: string;
12
+ /**
13
+ * Pins the host to the top of its scroll container (`position: sticky; top: 0`).
14
+ * Use with `PageScroll` or any ancestor that scrolls; portaled header content
15
+ * from `AppHeaderPortal` / `SybilionAppHeader` stays inside this element.
16
+ */
17
+ sticky?: boolean;
12
18
  };
13
19
 
14
20
  export function AppHeaderHost({
15
21
  className,
16
22
  anchorId = PAGE_HEADER_ID,
23
+ sticky,
17
24
  }: AppHeaderProps) {
18
- return <header className={cn(S.root, className)} id={anchorId} />;
25
+ return (
26
+ <header
27
+ className={cn(S.root, sticky && S.sticky, className)}
28
+ id={anchorId}
29
+ />
30
+ );
19
31
  }
20
32
 
21
33
  export type AppHeaderPortalProps = {
@@ -15,13 +15,18 @@ export function Chat({
15
15
  isEmpty,
16
16
  scopeId,
17
17
  onChatDeleted,
18
+ onNewChat,
18
19
  ...props
19
20
  }: ChatProps) {
20
21
  return (
21
22
  <div className={cn(S.root, className, isEmpty && S.isEmpty)} {...props}>
22
23
  {scopeId ? (
23
24
  <div className={S.header}>
24
- <ChatSelector id={scopeId} onChatDeleted={onChatDeleted} />
25
+ <ChatSelector
26
+ id={scopeId}
27
+ onChatDeleted={onChatDeleted}
28
+ onNewChat={onNewChat}
29
+ />
25
30
  </div>
26
31
  ) : null}
27
32
  {children}
@@ -25,6 +25,10 @@ export type ChatSendMessagePayload = {
25
25
  apiMessage: string;
26
26
  displayText: string;
27
27
  userTextFileAttachments?: UserTextFileAttachment[];
28
+ /** When true, skip adding a USER bubble before the API call (e.g. slash-command actions). */
29
+ omitUserMessage?: boolean;
30
+ /** Shimmer label while waiting on the API; defaults to "Thinking...". */
31
+ loadingLabel?: string;
28
32
  };
29
33
 
30
34
  export interface Message {
@@ -127,4 +131,6 @@ export interface ChatProps extends HTMLAttributes<HTMLDivElement> {
127
131
  /** Chat context scope; when set, header shows chat selector + delete. */
128
132
  scopeId?: string;
129
133
  onChatDeleted?: (sessionId: string) => void;
134
+ /** "+ New Chat" in the selector; when omitted, starts an empty session. */
135
+ onNewChat?: () => void;
130
136
  }
@@ -34,6 +34,7 @@ export function ChatChrome({
34
34
  onQuickReply,
35
35
  suppressedQuickReplyKeys,
36
36
  isLoading,
37
+ loadingLabel,
37
38
  scriptContinueLabel,
38
39
  onScriptContinue,
39
40
  renderMessageChart,
@@ -45,6 +46,7 @@ export function ChatChrome({
45
46
  effectiveScopeId,
46
47
  onPromptSubmit,
47
48
  onChatDeleted,
49
+ onNewChat,
48
50
  promptPrefill,
49
51
  footerClassName,
50
52
  emptyState,
@@ -174,6 +176,7 @@ export function ChatChrome({
174
176
  isEmpty={isEmpty}
175
177
  scopeId={effectiveScopeId}
176
178
  onChatDeleted={onChatDeleted}
179
+ onNewChat={onNewChat}
177
180
  >
178
181
  {isEmpty ? (
179
182
  <>
@@ -246,9 +249,9 @@ export function ChatChrome({
246
249
 
247
250
  {showInlinePresets && renderPresets('inline')}
248
251
 
249
- {isLoading && isLastMessageFromUser && (
252
+ {isLoading && (isLastMessageFromUser || loadingLabel) && (
250
253
  <TextShimmer duration={1} spread={5} className={S.loader}>
251
- Thinking...
254
+ {loadingLabel ?? 'Thinking...'}
252
255
  </TextShimmer>
253
256
  )}
254
257
  </Scroll>
@@ -33,6 +33,8 @@ export interface ChatChromeProps {
33
33
  onQuickReply: (branchKey: string, displayLabel: string) => void;
34
34
  suppressedQuickReplyKeys: ReadonlySet<string>;
35
35
  isLoading: boolean;
36
+ /** Overrides default "Thinking..." shimmer while waiting on the API. */
37
+ loadingLabel?: string;
36
38
  scriptContinueLabel: string | undefined;
37
39
  onScriptContinue: (() => void) | undefined;
38
40
  renderMessageChart?: () => React.ReactNode;
@@ -47,6 +49,8 @@ export interface ChatChromeProps {
47
49
  attachments?: ChatAttachmentDropItem[],
48
50
  ) => void | Promise<void>;
49
51
  onChatDeleted: (sessionId: string) => void;
52
+ /** "+ New Chat" in the header selector; omit for default empty session. */
53
+ onNewChat?: () => void;
50
54
  /** `?prompt=` deep link text for one-shot composer pre-fill. */
51
55
  promptPrefill?: string | null;
52
56
  footerClassName?: string;
@@ -18,19 +18,26 @@ export interface ChatSelectorProps {
18
18
  id: string;
19
19
  className?: string;
20
20
  onChatDeleted?: (sessionId: string) => void;
21
+ /** When set, used for "+ New Chat" instead of the default empty `newChat()`. */
22
+ onNewChat?: () => void;
21
23
  }
22
24
 
23
25
  export function ChatSelector({
24
26
  id,
25
27
  className,
26
28
  onChatDeleted,
29
+ onNewChat,
27
30
  }: ChatSelectorProps) {
28
31
  const { chats, currentChatId, setCurrentChatId, newChat, deleteChat } =
29
32
  useChatsForScopeId(id);
30
33
 
31
34
  const handleValueChange = (value: string) => {
32
35
  if (value === 'new') {
33
- newChat();
36
+ if (onNewChat) {
37
+ onNewChat();
38
+ } else {
39
+ newChat();
40
+ }
34
41
  } else if (value) {
35
42
  setCurrentChatId(value);
36
43
  }
@@ -9,7 +9,7 @@ import {
9
9
 
10
10
  export type ChatSheetActions = {
11
11
  open: () => void;
12
- /** Starts a new chat session and opens the panel (same as the new-chat keyboard shortcut). */
12
+ /** Starts an empty chat session and opens the panel (same as Cmd/Ctrl+0). */
13
13
  openNewChat: () => void;
14
14
  /** Starts a new chat, opens the panel, and pre-fills the composer (user sends manually). */
15
15
  openNewChatWithPrefill: (prompt: string) => void;
@@ -22,6 +22,7 @@ import type { ChatPresetsLayout } from '#uilib/components/ui/Chat/ChatPresets';
22
22
  import {
23
23
  buildChatSendMessagePayload,
24
24
  displayTextFromSendPayload,
25
+ loadingLabelFromSendPayload,
25
26
  } from '#uilib/components/ui/Chat/buildChatSendMessagePayload';
26
27
  import { usedPresetIdsFromMessages } from '#uilib/components/ui/Chat/chat-preset-utils';
27
28
  import {
@@ -86,6 +87,11 @@ export type UseChatPanelChromeModelInput = {
86
87
  slashCommandItems?: SlashCommandItem[];
87
88
  /** Custom slash command handler (palette pick or Enter on `/id`). */
88
89
  onSlashItemCommand?: SlashOnItemCommand;
90
+ /**
91
+ * When true, "+ New Chat" in the selector seeds from the current session.
92
+ * Keyboard shortcut and other new-chat entry points always start empty.
93
+ */
94
+ copyHistoryOnNewChat?: boolean;
89
95
  /** Override or extend the default send payload (e.g. api vs display text split). */
90
96
  transformSendPayload?: (
91
97
  message: string,
@@ -125,7 +131,7 @@ type PresetScriptState = {
125
131
  };
126
132
 
127
133
  const SCRIPT_STEP_DELAY_MS = 1200;
128
- const CHAT_NEW_SHORTCUT_KEY = 'o';
134
+ const CHAT_NEW_SHORTCUT_KEY = '0';
129
135
 
130
136
  const CHAT_QUERY_PARAM = 'chat';
131
137
  const CHAT_OPEN_VALUE = 'open';
@@ -144,6 +150,7 @@ export function useChatPanelChromeModel({
144
150
  onAttachmentsDropped,
145
151
  slashCommandItems,
146
152
  onSlashItemCommand,
153
+ copyHistoryOnNewChat = false,
147
154
  transformSendPayload,
148
155
  }: UseChatPanelChromeModelInput): UseChatPanelChromeModelResult {
149
156
  const effectiveScopeId = scopeId ?? NO_SCOPE_FALLBACK;
@@ -160,6 +167,9 @@ export function useChatPanelChromeModel({
160
167
  setChatPanelOpen,
161
168
  } = useSidebar();
162
169
  const [localUiBusy, setLocalUiBusy] = useState(false);
170
+ const [outboundLoadingLabel, setOutboundLoadingLabel] = useState<
171
+ string | undefined
172
+ >();
163
173
  const {
164
174
  chats,
165
175
  currentChatId,
@@ -337,9 +347,44 @@ export function useChatPanelChromeModel({
337
347
 
338
348
  const isEmpty = isChatEmpty(chat) && !isLoading;
339
349
 
350
+ const startEmptyNewChat = useCallback(() => {
351
+ const priorChatId = currentChatId;
352
+ const sessionId = newChat();
353
+ if (sessionId && priorChatId) {
354
+ endLocalDemoFlow(priorChatId);
355
+ }
356
+ if (sessionId) {
357
+ setPromptLinkPrefill(null);
358
+ }
359
+ return sessionId;
360
+ }, [newChat, currentChatId, endLocalDemoFlow]);
361
+
362
+ const startNewChatFromSelector = useCallback(() => {
363
+ const priorChatId = currentChatId;
364
+ const messages = chat?.messages ?? [];
365
+ const sessionId = newChat(
366
+ copyHistoryOnNewChat && messages.length > 0
367
+ ? { seedMessages: messages }
368
+ : undefined,
369
+ );
370
+ if (sessionId && priorChatId) {
371
+ endLocalDemoFlow(priorChatId);
372
+ }
373
+ if (sessionId) {
374
+ setPromptLinkPrefill(null);
375
+ }
376
+ return sessionId;
377
+ }, [
378
+ newChat,
379
+ copyHistoryOnNewChat,
380
+ chat?.messages,
381
+ currentChatId,
382
+ endLocalDemoFlow,
383
+ ]);
384
+
340
385
  const openNewChatWithPrefill = useCallback(
341
386
  (prompt: string) => {
342
- const sessionId = newChat();
387
+ const sessionId = startEmptyNewChat();
343
388
  if (sessionId == null) {
344
389
  logger.warn('Chat prefill: sign in to use the assistant.');
345
390
  return;
@@ -349,7 +394,7 @@ export function useChatPanelChromeModel({
349
394
  setPromptLinkPrefill(trimmed || null);
350
395
  onOpenChange(true);
351
396
  },
352
- [newChat, onOpenChange],
397
+ [startEmptyNewChat, onOpenChange],
353
398
  );
354
399
 
355
400
  /**
@@ -381,7 +426,7 @@ export function useChatPanelChromeModel({
381
426
  const needsFirstSession = chats.length === 0;
382
427
 
383
428
  if (!isEmpty || needsFirstSession) {
384
- const sessionId = newChat();
429
+ const sessionId = startEmptyNewChat();
385
430
  if (sessionId == null) {
386
431
  logger.warn('Chat prompt link: sign in to use the assistant.');
387
432
  mutateSearchParams(n => {
@@ -408,15 +453,15 @@ export function useChatPanelChromeModel({
408
453
  callback: (e: Event) => {
409
454
  const ke = e as KeyboardEvent;
410
455
  if (
411
- ke.key.toLowerCase() !== CHAT_NEW_SHORTCUT_KEY ||
456
+ ke.key !== CHAT_NEW_SHORTCUT_KEY ||
412
457
  !(ke.metaKey || ke.ctrlKey) ||
413
- !ke.shiftKey ||
458
+ ke.shiftKey ||
414
459
  ke.altKey
415
460
  ) {
416
461
  return;
417
462
  }
418
463
  ke.preventDefault();
419
- newChat();
464
+ startEmptyNewChat();
420
465
  onOpenChange(true);
421
466
  },
422
467
  });
@@ -667,8 +712,13 @@ export function useChatPanelChromeModel({
667
712
  payload,
668
713
  );
669
714
  }
670
- const assistantResponse = await sendMessage(payload);
671
- onMessage?.(displayTextFromSendPayload(payload), assistantResponse);
715
+ setOutboundLoadingLabel(loadingLabelFromSendPayload(payload));
716
+ try {
717
+ const assistantResponse = await sendMessage(payload);
718
+ onMessage?.(displayTextFromSendPayload(payload), assistantResponse);
719
+ } finally {
720
+ setOutboundLoadingLabel(undefined);
721
+ }
672
722
  } catch (error) {
673
723
  logger.error('Error sending chat message:', error);
674
724
  }
@@ -1181,6 +1231,7 @@ export function useChatPanelChromeModel({
1181
1231
  onQuickReply,
1182
1232
  suppressedQuickReplyKeys,
1183
1233
  isLoading,
1234
+ loadingLabel: outboundLoadingLabel,
1184
1235
  scriptContinueLabel,
1185
1236
  onScriptContinue,
1186
1237
  renderMessageChart,
@@ -1192,6 +1243,7 @@ export function useChatPanelChromeModel({
1192
1243
  effectiveScopeId,
1193
1244
  onPromptSubmit: onPromptSubmitWithSlashCommands,
1194
1245
  onChatDeleted: endLocalDemoFlow,
1246
+ onNewChat: startNewChatFromSelector,
1195
1247
  promptPrefill: promptLinkPrefill,
1196
1248
  emptyState: resolvedEmptyState,
1197
1249
  allowedAttachments,
@@ -1214,7 +1266,7 @@ export function useChatPanelChromeModel({
1214
1266
  isOpen,
1215
1267
  onOpenChange,
1216
1268
  toggleOpen,
1217
- newChat,
1269
+ newChat: startEmptyNewChat,
1218
1270
  openNewChatWithPrefill,
1219
1271
  chatPanelContainer,
1220
1272
  };
@@ -80,3 +80,10 @@ export function displayTextFromSendPayload(
80
80
  ): string {
81
81
  return typeof message === 'string' ? message : message.displayText;
82
82
  }
83
+
84
+ /** Optional loading shimmer label from a structured send payload. */
85
+ export function loadingLabelFromSendPayload(
86
+ message: string | ChatSendMessagePayload,
87
+ ): string | undefined {
88
+ return typeof message === 'string' ? undefined : message.loadingLabel;
89
+ }
@@ -33,6 +33,13 @@ export function AppShellMainContent({
33
33
 
34
34
  export const AppShell = forwardRef<HTMLDivElement, AppShellProps>(
35
35
  function AppShell({ className, ...props }, ref) {
36
- return <div ref={ref} className={cn(S.root, className)} {...props} />;
36
+ return (
37
+ <div
38
+ ref={ref}
39
+ data-slot="app-shell"
40
+ className={cn(S.root, className)}
41
+ {...props}
42
+ />
43
+ );
37
44
  },
38
45
  );
@@ -27,15 +27,27 @@
27
27
 
28
28
  @media (max-width MOBILE)
29
29
  left 32px
30
+
31
+ // Top-level shell: offset logo into sidebar column (same math as nested embed, but absolute — not fixed).
30
32
  @media (min-width MOBILE)
31
- :global([data-slot='sidebar-wrapper'][data-state='expanded']) &
32
- position fixed
33
+ :global([data-slot='sidebar-wrapper'][data-state='expanded']:not([data-slot='app-shell'] [data-slot='sidebar-wrapper']) [data-slot='app-shell']:not([data-slot='app-shell'] [data-slot='app-shell'])) &:global(:not([data-slot='app-shell'] [data-slot='app-shell'] *))
34
+ position absolute
35
+ left calc(-1 * var(--sidebar-width) + 52px)
36
+
37
+ // Nested shell (e.g. docs embed): absolute within header; offset into sidebar when expanded.
38
+ :global([data-slot='app-shell'] [data-slot='sidebar-wrapper'][data-state='expanded'] [data-slot='app-shell']) &
39
+ position absolute
40
+ left calc(-1 * var(--sidebar-width) + 44px)
41
+
42
+ :global([data-slot='app-shell'] [data-slot='sidebar-wrapper'][data-state='collapsed'] [data-slot='app-shell']) &
43
+ position absolute
44
+ left var(--p-8)
33
45
 
34
46
  .logoAreaWithBanner
35
47
  top calc(22px + var(--welcome-banner-height, 0px))
36
48
 
37
49
  @media (min-width MOBILE)
38
- :global([data-slot='sidebar-wrapper'][data-state='collapsed']) &
50
+ :global([data-slot='sidebar-wrapper'][data-state='collapsed']:not([data-slot='app-shell'] [data-slot='sidebar-wrapper']) [data-slot='app-shell']:not([data-slot='app-shell'] [data-slot='app-shell'])) &
39
51
  top 22px
40
52
 
41
53
  .logoLink
@@ -45,9 +45,14 @@ export type UpdateChatMessagePatch = {
45
45
  inProgress?: boolean;
46
46
  };
47
47
 
48
+ export type NewChatOptions = {
49
+ /** When set, seeds the new session's messages (e.g. continue dialog in reports). */
50
+ seedMessages?: readonly Message[];
51
+ };
52
+
48
53
  export interface ChatContextType {
49
54
  /** Returns the new session id, or undefined if no user / not created. */
50
- newChat: (scopeId: string) => string | undefined;
55
+ newChat: (scopeId: string, options?: NewChatOptions) => string | undefined;
51
56
  setCurrentChatId: (currScopeId: string, sessionId: string) => void;
52
57
  addMessage: (
53
58
  scopeId: string,
@@ -173,6 +178,22 @@ function addScopeIdToRegistry(scopeId: string) {
173
178
  }
174
179
  }
175
180
 
181
+ /** Shallow-clone messages for seeding another session; drops in-progress rows. */
182
+ function cloneMessagesForNewSession(messages: readonly Message[]): Message[] {
183
+ return messages
184
+ .filter(message => !message.inProgress)
185
+ .map(message => ({
186
+ ...message,
187
+ ...(message.userTextFileAttachments
188
+ ? {
189
+ userTextFileAttachments: message.userTextFileAttachments.map(
190
+ attachment => ({ ...attachment }),
191
+ ),
192
+ }
193
+ : {}),
194
+ }));
195
+ }
196
+
176
197
  export interface ChatProviderProps {
177
198
  children: ReactNode;
178
199
  /** When null, chat state is cleared (logged out). When set, only LS rows for scopes starting with `${userId}-` are loaded. */
@@ -249,15 +270,19 @@ export function ChatProvider({
249
270
  );
250
271
 
251
272
  const newChat = useCallback(
252
- (scopeId: string): string | undefined => {
273
+ (scopeId: string, options?: NewChatOptions): string | undefined => {
253
274
  if (userSwitchKey === null) return undefined;
254
275
  addScopeIdToRegistry(scopeId);
255
276
 
256
277
  const sessionId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
278
+ const seededMessages =
279
+ options?.seedMessages && options.seedMessages.length > 0
280
+ ? cloneMessagesForNewSession(options.seedMessages)
281
+ : [];
257
282
  const newChat: Chat = {
258
283
  session_id: sessionId,
259
284
  name: '',
260
- messages: [],
285
+ messages: seededMessages,
261
286
  };
262
287
 
263
288
  setChats(prev => {
@@ -469,7 +494,7 @@ export function ChatProvider({
469
494
 
470
495
  if (typeof message === 'string') {
471
496
  addMessage(scopeId, targetChatId, MessageRole.USER, message);
472
- } else {
497
+ } else if (!message.omitUserMessage) {
473
498
  addMessage(
474
499
  scopeId,
475
500
  targetChatId,
@@ -672,7 +697,7 @@ export function useChatsForScopeId(scopeId: string) {
672
697
  currentChatId,
673
698
  isOutboundPending,
674
699
  setCurrentChatId: (targetId: string) => setCurrentChatId(scopeId, targetId),
675
- newChat: () => newChat(scopeId),
700
+ newChat: (options?: NewChatOptions) => newChat(scopeId, options),
676
701
  addMessage: (
677
702
  chatId: string,
678
703
  role: MessageRole,
@@ -30,10 +30,22 @@ export function DocsShell() {
30
30
  onNavigate={href => {
31
31
  void navigate(href);
32
32
  }}
33
- authenticated={false}
33
+ authenticated
34
34
  isAuthenticated={false}
35
35
  onLogout={() => undefined}
36
36
  signInSlot={<ThemeToggle />}
37
+ defaultApps={[
38
+ {
39
+ id: 'docs-home',
40
+ displayName: 'Home',
41
+ subtitle: 'Home',
42
+ iconKey: 'grid-four',
43
+ accent: '#0d9488',
44
+ accentMuted: 'rgba(13, 148, 136, 0.12)',
45
+ href: '/',
46
+ matchPathPrefixes: ['/'],
47
+ },
48
+ ]}
37
49
  />
38
50
  <Outlet />
39
51
  </AppShellMainContent>
@@ -14,6 +14,9 @@
14
14
  display flex
15
15
  flex-direction column
16
16
 
17
+ // .preview > :global([data-slot='sidebar-wrapper'][data-state='expanded']) .previewLogoArea
18
+ // margin-left calc(-1 * var(--sidebar-width)) !important
19
+
17
20
  .previewScrollRoot
18
21
  flex 1
19
22
  min-height 0
@@ -113,7 +113,7 @@ function DemoMainBody({ panel }: { panel: PreviewPanel }) {
113
113
  </PageContentSection>
114
114
  <PageContentSection title="Scroll test">
115
115
  <div className={S.scrollTestBlock}>
116
- Placeholder region with <code>min-height: 100vh</code> — scroll the
116
+ Placeholder region with <code>min-height: 160vh</code> — scroll the
117
117
  main column to verify PageScroll / AppShell behavior.
118
118
  </div>
119
119
  </PageContentSection>
@@ -130,55 +130,60 @@ function StandaloneLayoutPreview() {
130
130
  >();
131
131
 
132
132
  return (
133
- <div className={S.preview}>
134
- <PageScroll rootClassName={S.previewScrollRoot}>
135
- <AppShell>
136
- <DemoAppSidebar
137
- panel={panel}
138
- onSelectPanel={setPanel}
139
- selectedDatasetId={selectedDatasetId}
140
- onSelectDatasetId={setSelectedDatasetId}
141
- />
133
+ <div className={S.preview} data-standalone-layout-preview>
134
+ <SidebarProvider
135
+ sidebarWidthStorageKey="uilib.docs.standaloneLayout.sidebarWidthPx"
136
+ persistSidebarWidthWithoutConsent
137
+ >
138
+ <PageScroll rootClassName={S.previewScrollRoot}>
139
+ <AppShell>
140
+ <DemoAppSidebar
141
+ panel={panel}
142
+ onSelectPanel={setPanel}
143
+ selectedDatasetId={selectedDatasetId}
144
+ onSelectDatasetId={setSelectedDatasetId}
145
+ />
142
146
 
143
- <AppShellMainContent
144
- header={<AppHeaderHost anchorId={TEST_HEADER_ID} />}
145
- footer={
146
- <PageFooter
147
- versionLink="#standalone-layout-preview"
148
- versionLabel="0.0.0-preview"
149
- />
150
- }
151
- >
152
- <SybilionAppHeader
153
- pageHeaderId={TEST_HEADER_ID}
154
- pathname={previewPath}
155
- onNavigate={setPreviewPath}
156
- authenticated
157
- appsStorageKey={DOCS_WORKSPACE_APPS_LS_KEY}
158
- defaultApps={DOCS_PREVIEW_APPS}
159
- user={{
160
- name: 'Example User',
161
- email: 'user@example.com',
162
- avatar: '',
163
- }}
164
- onLogout={() => undefined}
165
- menuItems={
166
- <>
167
- <DropdownMenuItem>
168
- <UserCircleIcon />
169
- Account (preview)
170
- </DropdownMenuItem>
171
- <DropdownMenuItem>
172
- <GearSixIcon />
173
- Settings (preview)
174
- </DropdownMenuItem>
175
- </>
147
+ <AppShellMainContent
148
+ header={<AppHeaderHost sticky anchorId={TEST_HEADER_ID} />}
149
+ footer={
150
+ <PageFooter
151
+ versionLink="#standalone-layout-preview"
152
+ versionLabel="0.0.0-preview"
153
+ />
176
154
  }
177
- />
178
- <DemoMainBody panel={panel} />
179
- </AppShellMainContent>
180
- </AppShell>
181
- </PageScroll>
155
+ >
156
+ <SybilionAppHeader
157
+ pageHeaderId={TEST_HEADER_ID}
158
+ pathname={previewPath}
159
+ onNavigate={setPreviewPath}
160
+ authenticated
161
+ appsStorageKey={DOCS_WORKSPACE_APPS_LS_KEY}
162
+ defaultApps={DOCS_PREVIEW_APPS}
163
+ user={{
164
+ name: 'Example User',
165
+ email: 'user@example.com',
166
+ avatar: '',
167
+ }}
168
+ onLogout={() => undefined}
169
+ menuItems={
170
+ <>
171
+ <DropdownMenuItem>
172
+ <UserCircleIcon />
173
+ Account (preview)
174
+ </DropdownMenuItem>
175
+ <DropdownMenuItem>
176
+ <GearSixIcon />
177
+ Settings (preview)
178
+ </DropdownMenuItem>
179
+ </>
180
+ }
181
+ />
182
+ <DemoMainBody panel={panel} />
183
+ </AppShellMainContent>
184
+ </AppShell>
185
+ </PageScroll>
186
+ </SidebarProvider>
182
187
  </div>
183
188
  );
184
189
  }
@@ -199,12 +204,7 @@ export default function StandaloneAppLayoutPage() {
199
204
  actions={<DocsHeaderActions />}
200
205
  />
201
206
  <PageContentSection title="Embedded mini-app (layout preview)">
202
- <SidebarProvider
203
- sidebarWidthStorageKey="uilib.docs.standaloneLayout.sidebarWidthPx"
204
- persistSidebarWidthWithoutConsent
205
- >
206
- <StandaloneLayoutPreview />
207
- </SidebarProvider>
207
+ <StandaloneLayoutPreview />
208
208
  </PageContentSection>
209
209
  </>
210
210
  );
@@ -43,12 +43,6 @@ export const DOC_REGISTRY: DocEntry[] = [
43
43
  section: 'Data display',
44
44
  load: () => import('./pages/AnalysisLineIconPage'),
45
45
  },
46
- // {
47
- // slug: 'app-header',
48
- // title: 'AppHeader',
49
- // section: 'Layout',
50
- // load: () => import('./pages/AppHeaderPage'),
51
- // },
52
46
  {
53
47
  slug: 'avatar',
54
48
  title: 'Avatar',