@sybilion/uilib 1.3.73 → 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 (29) 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/ChatChrome/ChatChrome.js +2 -2
  4. package/dist/esm/components/ui/Chat/ChatSheet/useChatPanelChromeModel.js +11 -3
  5. package/dist/esm/components/ui/Chat/buildChatSendMessagePayload.js +5 -1
  6. package/dist/esm/components/ui/Page/AppShell/AppShell.js +1 -1
  7. package/dist/esm/components/widgets/SybilionAppHeader/SybilionAppHeader.styl.js +1 -1
  8. package/dist/esm/contexts/chat-context.js +1 -1
  9. package/dist/esm/types/src/components/ui/AppHeader/AppHeader.d.ts +7 -1
  10. package/dist/esm/types/src/components/ui/Chat/Chat.types.d.ts +4 -0
  11. package/dist/esm/types/src/components/ui/Chat/ChatChrome/ChatChrome.d.ts +1 -1
  12. package/dist/esm/types/src/components/ui/Chat/ChatChrome/ChatChrome.types.d.ts +2 -0
  13. package/dist/esm/types/src/components/ui/Chat/buildChatSendMessagePayload.d.ts +2 -0
  14. package/package.json +1 -1
  15. package/src/components/ui/AppHeader/AppHeader.styl +9 -0
  16. package/src/components/ui/AppHeader/AppHeader.styl.d.ts +1 -0
  17. package/src/components/ui/AppHeader/AppHeader.tsx +13 -1
  18. package/src/components/ui/Chat/Chat.types.ts +4 -0
  19. package/src/components/ui/Chat/ChatChrome/ChatChrome.tsx +3 -2
  20. package/src/components/ui/Chat/ChatChrome/ChatChrome.types.ts +2 -0
  21. package/src/components/ui/Chat/ChatSheet/useChatPanelChromeModel.tsx +12 -2
  22. package/src/components/ui/Chat/buildChatSendMessagePayload.ts +7 -0
  23. package/src/components/ui/Page/AppShell/AppShell.tsx +8 -1
  24. package/src/components/widgets/SybilionAppHeader/SybilionAppHeader.styl +15 -3
  25. package/src/contexts/chat-context.tsx +1 -1
  26. package/src/docs/DocsShell.tsx +13 -1
  27. package/src/docs/pages/StandaloneAppLayoutPage/StandaloneAppLayoutPage.styl +3 -0
  28. package/src/docs/pages/StandaloneAppLayoutPage/StandaloneAppLayoutPage.tsx +54 -54
  29. 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 };
@@ -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, onNewChat, 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]);
@@ -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 };
@@ -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';
@@ -27,6 +27,7 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
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);
@@ -464,8 +465,14 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
464
465
  if (transformSendPayload) {
465
466
  payload = await transformSendPayload(message, stagedAttachments, payload);
466
467
  }
467
- const assistantResponse = await sendMessage(payload);
468
- 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
+ }
469
476
  }
470
477
  catch (error) {
471
478
  logger.error('Error sending chat message:', error);
@@ -895,6 +902,7 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
895
902
  onQuickReply,
896
903
  suppressedQuickReplyKeys,
897
904
  isLoading,
905
+ loadingLabel: outboundLoadingLabel,
898
906
  scriptContinueLabel,
899
907
  onScriptContinue,
900
908
  renderMessageChart,
@@ -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
 
@@ -312,7 +312,7 @@ function ChatProvider({ children, userSwitchKey, sendChatMessage: sendChatMessag
312
312
  if (typeof message === 'string') {
313
313
  addMessage(scopeId, targetChatId, MessageRole.USER, message);
314
314
  }
315
- else {
315
+ else if (!message.omitUserMessage) {
316
316
  addMessage(scopeId, targetChatId, MessageRole.USER, message.displayText, {
317
317
  userTextFileAttachments: normalizeUserTextFileAttachments(message),
318
318
  });
@@ -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;
@@ -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;
@@ -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, 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, 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;
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sybilion/uilib",
3
- "version": "1.3.73",
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 = {
@@ -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 {
@@ -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,
@@ -248,9 +249,9 @@ export function ChatChrome({
248
249
 
249
250
  {showInlinePresets && renderPresets('inline')}
250
251
 
251
- {isLoading && isLastMessageFromUser && (
252
+ {isLoading && (isLastMessageFromUser || loadingLabel) && (
252
253
  <TextShimmer duration={1} spread={5} className={S.loader}>
253
- Thinking...
254
+ {loadingLabel ?? 'Thinking...'}
254
255
  </TextShimmer>
255
256
  )}
256
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;
@@ -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 {
@@ -166,6 +167,9 @@ export function useChatPanelChromeModel({
166
167
  setChatPanelOpen,
167
168
  } = useSidebar();
168
169
  const [localUiBusy, setLocalUiBusy] = useState(false);
170
+ const [outboundLoadingLabel, setOutboundLoadingLabel] = useState<
171
+ string | undefined
172
+ >();
169
173
  const {
170
174
  chats,
171
175
  currentChatId,
@@ -708,8 +712,13 @@ export function useChatPanelChromeModel({
708
712
  payload,
709
713
  );
710
714
  }
711
- const assistantResponse = await sendMessage(payload);
712
- 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
+ }
713
722
  } catch (error) {
714
723
  logger.error('Error sending chat message:', error);
715
724
  }
@@ -1222,6 +1231,7 @@ export function useChatPanelChromeModel({
1222
1231
  onQuickReply,
1223
1232
  suppressedQuickReplyKeys,
1224
1233
  isLoading,
1234
+ loadingLabel: outboundLoadingLabel,
1225
1235
  scriptContinueLabel,
1226
1236
  onScriptContinue,
1227
1237
  renderMessageChart,
@@ -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
@@ -494,7 +494,7 @@ export function ChatProvider({
494
494
 
495
495
  if (typeof message === 'string') {
496
496
  addMessage(scopeId, targetChatId, MessageRole.USER, message);
497
- } else {
497
+ } else if (!message.omitUserMessage) {
498
498
  addMessage(
499
499
  scopeId,
500
500
  targetChatId,
@@ -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',