@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.
- package/dist/esm/components/ui/AppHeader/AppHeader.js +2 -2
- package/dist/esm/components/ui/AppHeader/AppHeader.styl.js +2 -2
- package/dist/esm/components/ui/Chat/Chat.js +2 -2
- package/dist/esm/components/ui/Chat/ChatChrome/ChatChrome.js +3 -3
- package/dist/esm/components/ui/Chat/ChatSheet/ChatSelector.js +7 -2
- package/dist/esm/components/ui/Chat/ChatSheet/useChatPanelChromeModel.js +52 -12
- package/dist/esm/components/ui/Chat/buildChatSendMessagePayload.js +5 -1
- package/dist/esm/components/ui/Page/AppShell/AppShell.js +1 -1
- package/dist/esm/components/widgets/SybilionAppHeader/SybilionAppHeader.styl.js +1 -1
- package/dist/esm/contexts/chat-context.js +20 -4
- package/dist/esm/types/src/components/ui/AppHeader/AppHeader.d.ts +7 -1
- package/dist/esm/types/src/components/ui/Chat/Chat.d.ts +1 -1
- package/dist/esm/types/src/components/ui/Chat/Chat.types.d.ts +6 -0
- package/dist/esm/types/src/components/ui/Chat/ChatChrome/ChatChrome.d.ts +1 -1
- package/dist/esm/types/src/components/ui/Chat/ChatChrome/ChatChrome.types.d.ts +4 -0
- package/dist/esm/types/src/components/ui/Chat/ChatSheet/ChatSelector.d.ts +3 -1
- package/dist/esm/types/src/components/ui/Chat/ChatSheet/ChatSheet.d.ts +1 -1
- package/dist/esm/types/src/components/ui/Chat/ChatSheet/useChatPanelChromeModel.d.ts +6 -1
- package/dist/esm/types/src/components/ui/Chat/buildChatSendMessagePayload.d.ts +2 -0
- package/dist/esm/types/src/contexts/chat-context.d.ts +7 -3
- package/package.json +1 -1
- package/src/components/ui/AppHeader/AppHeader.styl +9 -0
- package/src/components/ui/AppHeader/AppHeader.styl.d.ts +1 -0
- package/src/components/ui/AppHeader/AppHeader.tsx +13 -1
- package/src/components/ui/Chat/Chat.tsx +6 -1
- package/src/components/ui/Chat/Chat.types.ts +6 -0
- package/src/components/ui/Chat/ChatChrome/ChatChrome.tsx +5 -2
- package/src/components/ui/Chat/ChatChrome/ChatChrome.types.ts +4 -0
- package/src/components/ui/Chat/ChatSheet/ChatSelector.tsx +8 -1
- package/src/components/ui/Chat/ChatSheet/ChatSheet.tsx +1 -1
- package/src/components/ui/Chat/ChatSheet/useChatPanelChromeModel.tsx +62 -10
- package/src/components/ui/Chat/buildChatSendMessagePayload.ts +7 -0
- package/src/components/ui/Page/AppShell/AppShell.tsx +8 -1
- package/src/components/widgets/SybilionAppHeader/SybilionAppHeader.styl +15 -3
- package/src/contexts/chat-context.tsx +30 -5
- package/src/docs/DocsShell.tsx +13 -1
- package/src/docs/pages/StandaloneAppLayoutPage/StandaloneAppLayoutPage.styl +3 -0
- package/src/docs/pages/StandaloneAppLayoutPage/StandaloneAppLayoutPage.tsx +54 -54
- 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:
|
|
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
|
-
|
|
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 = '
|
|
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
|
|
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
|
-
}, [
|
|
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 =
|
|
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
|
|
251
|
+
if (ke.key !== CHAT_NEW_SHORTCUT_KEY ||
|
|
220
252
|
!(ke.metaKey || ke.ctrlKey) ||
|
|
221
|
-
|
|
253
|
+
ke.shiftKey ||
|
|
222
254
|
ke.altKey) {
|
|
223
255
|
return;
|
|
224
256
|
}
|
|
225
257
|
ke.preventDefault();
|
|
226
|
-
|
|
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
|
-
|
|
437
|
-
|
|
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:
|
|
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
|
|
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,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
|
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
|
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 = '
|
|
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 =
|
|
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
|
-
[
|
|
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 =
|
|
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
|
|
456
|
+
ke.key !== CHAT_NEW_SHORTCUT_KEY ||
|
|
412
457
|
!(ke.metaKey || ke.ctrlKey) ||
|
|
413
|
-
|
|
458
|
+
ke.shiftKey ||
|
|
414
459
|
ke.altKey
|
|
415
460
|
) {
|
|
416
461
|
return;
|
|
417
462
|
}
|
|
418
463
|
ke.preventDefault();
|
|
419
|
-
|
|
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
|
-
|
|
671
|
-
|
|
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
|
|
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
|
|
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,
|
package/src/docs/DocsShell.tsx
CHANGED
|
@@ -30,10 +30,22 @@ export function DocsShell() {
|
|
|
30
30
|
onNavigate={href => {
|
|
31
31
|
void navigate(href);
|
|
32
32
|
}}
|
|
33
|
-
authenticated
|
|
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>
|
|
@@ -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:
|
|
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
|
-
<
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
<
|
|
203
|
-
sidebarWidthStorageKey="uilib.docs.standaloneLayout.sidebarWidthPx"
|
|
204
|
-
persistSidebarWidthWithoutConsent
|
|
205
|
-
>
|
|
206
|
-
<StandaloneLayoutPreview />
|
|
207
|
-
</SidebarProvider>
|
|
207
|
+
<StandaloneLayoutPreview />
|
|
208
208
|
</PageContentSection>
|
|
209
209
|
</>
|
|
210
210
|
);
|
package/src/docs/registry.ts
CHANGED
|
@@ -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',
|