@sybilion/uilib 1.3.67 → 1.3.70
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/Chat/ChatMessage/ChatMessage.styl.js +1 -1
- package/dist/esm/components/ui/Chat/ChatPrompt/useChatPromptEditor.js +4 -1
- package/dist/esm/components/ui/Chat/ChatSheet/chatPanelOpenSync.js +5 -1
- package/dist/esm/components/ui/Chat/ChatSheet/useChatPanelChromeModel.js +41 -18
- package/dist/esm/components/ui/Tooltip/Tooltip.styl.js +1 -1
- package/dist/esm/components/widgets/SybilionAppHeader/SybilionAppHeader.js +1 -1
- package/dist/esm/components/widgets/SybilionAppHeader/SybilionAppHeader.styl.js +2 -2
- package/dist/esm/contexts/chat-context.js +18 -0
- package/dist/esm/types/src/components/ui/Chat/ChatSheet/chatPanelOpenSync.d.ts +4 -0
- package/dist/esm/types/src/contexts/chat-context.d.ts +3 -1
- package/dist/esm/types/src/docs/pages/ChatComposerPrefillPage.d.ts +1 -0
- package/package.json +1 -1
- package/src/components/ui/Chat/ChatMessage/ChatMessage.styl +1 -1
- package/src/components/ui/Chat/ChatPrompt/useChatPromptEditor.ts +4 -1
- package/src/components/ui/Chat/ChatSheet/chatPanelOpenSync.test.ts +26 -0
- package/src/components/ui/Chat/ChatSheet/chatPanelOpenSync.ts +16 -0
- package/src/components/ui/Chat/ChatSheet/useChatPanelChromeModel.tsx +46 -15
- package/src/components/ui/Tooltip/Tooltip.styl +4 -4
- package/src/components/widgets/SybilionAppHeader/SybilionAppHeader.styl +9 -0
- package/src/components/widgets/SybilionAppHeader/SybilionAppHeader.styl.d.ts +1 -0
- package/src/components/widgets/SybilionAppHeader/SybilionAppHeader.tsx +2 -4
- package/src/contexts/chat-context.tsx +29 -0
- package/src/docs/pages/ChatComposerPrefillPage.tsx +202 -0
- package/src/docs/registry.ts +6 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import styleInject from 'style-inject';
|
|
2
2
|
|
|
3
|
-
var css_248z = ".ChatMessage_root__6rnsF{background:var(--bg-secondary);display:flex;flex-direction:column;gap:var(--p-1);padding:var(--p-4);padding-bottom:var(--p-1)}.ChatMessage_text__Y1XNR{color:var(--text-secondary);font-size:var(--text-sm);max-width:100%;min-width:0;overflow-wrap:anywhere;-webkit-user-select:text;-moz-user-select:text;user-select:text;width:-moz-fit-content;width:fit-content;word-break:break-word}.ChatMessage_role-assistant__wketE+.ChatMessage_role-assistant__wketE,.ChatMessage_role-system__g13OP+.ChatMessage_role-system__g13OP,.ChatMessage_role-user__u4JPV+.ChatMessage_role-user__u4JPV{padding-top:var(--p-1)}.ChatMessage_role-user__u4JPV{align-items:flex-end;max-width:100%;min-width:0}.ChatMessage_role-user__u4JPV .ChatMessage_userColumn__cQM6-{align-items:flex-end;display:flex;flex-direction:column;gap:var(--p-2);max-width:100%;min-width:0}.ChatMessage_role-user__u4JPV .ChatMessage_text__Y1XNR{background-color:var(--sb-slate-100);border-radius:var(--p-4);border-bottom-right-radius:0;box-sizing:border-box;overflow:hidden;padding:var(--p-3) var(--p-4);white-space:pre-wrap}.dark .ChatMessage_role-user__u4JPV .ChatMessage_text__Y1XNR{background-color:var(--sb-gray-
|
|
3
|
+
var css_248z = ".ChatMessage_root__6rnsF{background:var(--bg-secondary);display:flex;flex-direction:column;gap:var(--p-1);padding:var(--p-4);padding-bottom:var(--p-1)}.ChatMessage_text__Y1XNR{color:var(--text-secondary);font-size:var(--text-sm);max-width:100%;min-width:0;overflow-wrap:anywhere;-webkit-user-select:text;-moz-user-select:text;user-select:text;width:-moz-fit-content;width:fit-content;word-break:break-word}.ChatMessage_role-assistant__wketE+.ChatMessage_role-assistant__wketE,.ChatMessage_role-system__g13OP+.ChatMessage_role-system__g13OP,.ChatMessage_role-user__u4JPV+.ChatMessage_role-user__u4JPV{padding-top:var(--p-1)}.ChatMessage_role-user__u4JPV{align-items:flex-end;max-width:100%;min-width:0}.ChatMessage_role-user__u4JPV .ChatMessage_userColumn__cQM6-{align-items:flex-end;display:flex;flex-direction:column;gap:var(--p-2);max-width:100%;min-width:0}.ChatMessage_role-user__u4JPV .ChatMessage_text__Y1XNR{background-color:var(--sb-slate-100);border-radius:var(--p-4);border-bottom-right-radius:0;box-sizing:border-box;overflow:hidden;padding:var(--p-3) var(--p-4);white-space:pre-wrap}.dark .ChatMessage_role-user__u4JPV .ChatMessage_text__Y1XNR{background-color:var(--sb-gray-900)}.ChatMessage_role-system__g13OP{align-items:center}.ChatMessage_role-system__g13OP .ChatMessage_text__Y1XNR{color:var(--muted-foreground);width:100%}.ChatMessage_role-assistant__wketE .ChatMessage_text__Y1XNR{width:100%}.ChatMessage_role-assistant__wketE h3{line-height:2.4}.ChatMessage_role-assistant__wketE h4{font-size:1.1em;font-weight:600;line-height:2.2}.ChatMessage_role-assistant__wketE .ChatMessage_bullet__6vAhq{display:inline-block;margin-left:4px;margin-right:6px}.ChatMessage_role-assistant__wketE .ChatMessage_bullet__6vAhq:before{color:var(--text-secondary);content:\"•\";display:inline-block}.ChatMessage_role-assistant__wketE .ChatMessage_scrollHorizontal__Rms9n{max-width:100%}.ChatMessage_role-assistant__wketE table{border:1px solid var(--border);border-collapse:collapse;border-radius:var(--p-2);border-spacing:0;margin:var(--p-4) 0;overflow:hidden}.ChatMessage_role-assistant__wketE table td,.ChatMessage_role-assistant__wketE table th{border:1px solid var(--border);min-width:100px;padding:var(--p-1)}.ChatMessage_role-assistant__wketE table th{text-align:left}.ChatMessage_role-assistant__wketE ol,.ChatMessage_role-assistant__wketE ul{padding-left:var(--p-4)}.ChatMessage_role-assistant__wketE ul{list-style-type:disc}.ChatMessage_role-assistant__wketE ol{list-style-type:decimal}.ChatMessage_role-assistant__wketE .ChatMessage_datasetAppLink__Pxy-T{align-items:center;border:1px dashed var(--sb-slate-300);border-radius:8px;color:var(--foreground);display:inline-flex;font-size:var(--text-xs);gap:6px;margin:1px;max-width:100%;padding:2px 6px 2px 4px;text-decoration:none;vertical-align:middle}.ChatMessage_role-assistant__wketE .ChatMessage_datasetAppLink__Pxy-T:hover{background-color:var(--sb-slate-50);border-color:var(--sb-slate-400);border-style:solid}.dark .ChatMessage_role-assistant__wketE .ChatMessage_datasetAppLink__Pxy-T{border-color:var(--sb-gray-600)}.dark .ChatMessage_role-assistant__wketE .ChatMessage_datasetAppLink__Pxy-T:hover{background-color:var(--sb-gray-900)}.ChatMessage_role-assistant__wketE .ChatMessage_datasetAppLinkLabel__PMU7e{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ChatMessage_role-assistant__wketE .ChatMessage_quickReplyWrap__1UFyD{display:inline-block;margin:var(--p-1) var(--p-1) var(--p-1) 0;vertical-align:middle}.ChatMessage_role-assistant__wketE .ChatMessage_downloadButtons__RygM-{display:flex;gap:var(--p-2);margin-top:var(--p-4)}.ChatMessage_role-assistant__wketE .ChatMessage_downloadCard__NsNRa{align-items:center;background-color:var(--background);border-radius:var(--p-3);box-shadow:0 0 0 1px var(--border);cursor:pointer;display:flex;gap:var(--p-4);margin-top:var(--p-3);padding:var(--p-3);padding-right:var(--p-4);transition:all .15s;width:-moz-fit-content;width:fit-content}.ChatMessage_role-assistant__wketE .ChatMessage_downloadCard__NsNRa:hover{background-color:var(--sb-gray-50);border-color:var(--border)}.dark .ChatMessage_role-assistant__wketE .ChatMessage_downloadCard__NsNRa{background-color:var(--sb-gray-900);border-color:var(--border)}.dark .ChatMessage_role-assistant__wketE .ChatMessage_downloadCard__NsNRa:hover{background-color:var(--sb-gray-800)}.ChatMessage_role-assistant__wketE .ChatMessage_downloadCardIcon__jkxDJ{align-items:center;border-radius:var(--p-2);display:flex;flex-shrink:0;height:32px;justify-content:center;width:32px}.ChatMessage_role-assistant__wketE .ChatMessage_downloadCardContent__PTPwz{display:flex;flex:1;flex-direction:column;min-width:0}.ChatMessage_role-assistant__wketE .ChatMessage_downloadCardTitle__K1wqr{font-size:var(--text-base);font-weight:600;line-height:1.4}.ChatMessage_role-assistant__wketE .ChatMessage_downloadCardSubtitle__fVeF2{color:var(--muted-foreground);font-size:var(--text-sm);line-height:1.4}";
|
|
4
4
|
var S = {"root":"ChatMessage_root__6rnsF","text":"ChatMessage_text__Y1XNR","role-user":"ChatMessage_role-user__u4JPV","role-assistant":"ChatMessage_role-assistant__wketE","role-system":"ChatMessage_role-system__g13OP","userColumn":"ChatMessage_userColumn__cQM6-","bullet":"ChatMessage_bullet__6vAhq","scrollHorizontal":"ChatMessage_scrollHorizontal__Rms9n","datasetAppLink":"ChatMessage_datasetAppLink__Pxy-T","datasetAppLinkLabel":"ChatMessage_datasetAppLinkLabel__PMU7e","quickReplyWrap":"ChatMessage_quickReplyWrap__1UFyD","downloadButtons":"ChatMessage_downloadButtons__RygM-","downloadCard":"ChatMessage_downloadCard__NsNRa","downloadCardIcon":"ChatMessage_downloadCardIcon__jkxDJ","downloadCardContent":"ChatMessage_downloadCardContent__PTPwz","downloadCardTitle":"ChatMessage_downloadCardTitle__K1wqr","downloadCardSubtitle":"ChatMessage_downloadCardSubtitle__fVeF2"};
|
|
5
5
|
styleInject(css_248z);
|
|
6
6
|
|
|
@@ -143,8 +143,11 @@ function useChatPromptEditor({ disabled, placeholder, slashCommandItems, onSlash
|
|
|
143
143
|
const dom = chatPromptSafeEditorDom(editor);
|
|
144
144
|
if (dom)
|
|
145
145
|
syncChatPromptComposerHeight(dom, t);
|
|
146
|
+
if (!disabled && !editor.isDestroyed) {
|
|
147
|
+
editor.chain().focus('end').run();
|
|
148
|
+
}
|
|
146
149
|
});
|
|
147
|
-
}, [editor, prefillMessage]);
|
|
150
|
+
}, [editor, prefillMessage, disabled]);
|
|
148
151
|
useLayoutEffect(() => {
|
|
149
152
|
if (!editor)
|
|
150
153
|
return;
|
|
@@ -14,6 +14,10 @@ function shouldHealChatShellDesync(chatOpen, shellChatPanelOpen, layoutDismissed
|
|
|
14
14
|
function shouldCloseStaleLocalChatOpen(shellChatPanelOpen, chatOpen, isOpen) {
|
|
15
15
|
return !shellChatPanelOpen && !chatOpen && isOpen;
|
|
16
16
|
}
|
|
17
|
+
/** Shell still reserves width but no instance is showing chat (e.g. route change dropped `?chat=`). */
|
|
18
|
+
function shouldCloseOrphanShellChat(shellChatPanelOpen, chatOpen, isOpen) {
|
|
19
|
+
return shellChatPanelOpen && !chatOpen && !isOpen;
|
|
20
|
+
}
|
|
17
21
|
function shouldDismissChatAfterShellClosed(params) {
|
|
18
22
|
const { shellChatPanelOpen, wasShellChatPanelOpen, chatOpen, isOpen, openedShellChat, } = params;
|
|
19
23
|
if (shellChatPanelOpen || !wasShellChatPanelOpen || !chatOpen) {
|
|
@@ -25,4 +29,4 @@ function isChatPanelVisible(isOpen, shellChatPanelOpen) {
|
|
|
25
29
|
return isOpen && shellChatPanelOpen;
|
|
26
30
|
}
|
|
27
31
|
|
|
28
|
-
export { isChatPanelVisible, shouldCloseStaleLocalChatOpen, shouldDismissChatAfterShellClosed, shouldHealChatShellDesync, shouldOpenChatFromUrl };
|
|
32
|
+
export { isChatPanelVisible, shouldCloseOrphanShellChat, shouldCloseStaleLocalChatOpen, shouldDismissChatAfterShellClosed, shouldHealChatShellDesync, shouldOpenChatFromUrl };
|
|
@@ -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 {
|
|
5
|
+
import { displayTextFromSendPayload, buildChatSendMessagePayload } 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';
|
|
@@ -13,7 +13,7 @@ import logger from '../../../../lib/logger.js';
|
|
|
13
13
|
import { mergePresetFreeformDefaults } from '../../../../utils/chatPresetMerge.js';
|
|
14
14
|
import { useSidebar, DISMISS_CHAT_FOR_LAYOUT_EVENT } from '../../Sidebar/Sidebar.js';
|
|
15
15
|
import { Chat } from '../Chat.js';
|
|
16
|
-
import { shouldOpenChatFromUrl, shouldHealChatShellDesync, shouldCloseStaleLocalChatOpen, shouldDismissChatAfterShellClosed, isChatPanelVisible } from './chatPanelOpenSync.js';
|
|
16
|
+
import { shouldOpenChatFromUrl, shouldHealChatShellDesync, shouldCloseStaleLocalChatOpen, shouldCloseOrphanShellChat, shouldDismissChatAfterShellClosed, isChatPanelVisible } from './chatPanelOpenSync.js';
|
|
17
17
|
|
|
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__';
|
|
@@ -45,17 +45,20 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
|
|
|
45
45
|
/** Nav-forced chat close; blocks URL sync from reopening chat in the same tick. */
|
|
46
46
|
const layoutDismissedRef = useRef(false);
|
|
47
47
|
const panelActive = embedAsPage || isOpen;
|
|
48
|
-
// Ensure
|
|
48
|
+
// Ensure a renderable session when the panel is active (pick existing or create).
|
|
49
49
|
useEffect(() => {
|
|
50
|
-
if (panelActive
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
50
|
+
if (!panelActive)
|
|
51
|
+
return;
|
|
52
|
+
const currentValid = currentChatId != null &&
|
|
53
|
+
currentChatId !== '' &&
|
|
54
|
+
chats.some(chat => chat.session_id === currentChatId);
|
|
55
|
+
if (currentValid)
|
|
56
|
+
return;
|
|
57
|
+
if (chats.length > 0) {
|
|
58
|
+
setCurrentChatId(chats[0].session_id);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
newChat();
|
|
59
62
|
}
|
|
60
63
|
}, [panelActive, currentChatId, chats, setCurrentChatId, newChat]);
|
|
61
64
|
const [scriptByChatId, setScriptByChatId] = useState({});
|
|
@@ -294,8 +297,12 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
|
|
|
294
297
|
endLocalDemoFlow(chatId);
|
|
295
298
|
void (async () => {
|
|
296
299
|
try {
|
|
297
|
-
|
|
298
|
-
|
|
300
|
+
let payload = displayLabel;
|
|
301
|
+
if (transformSendPayload) {
|
|
302
|
+
payload = await transformSendPayload(displayLabel, undefined, displayLabel);
|
|
303
|
+
}
|
|
304
|
+
const assistantResponse = await sendMessage(payload);
|
|
305
|
+
onMessage?.(displayTextFromSendPayload(payload), assistantResponse);
|
|
299
306
|
}
|
|
300
307
|
catch (error) {
|
|
301
308
|
logger.error('Error sending chat message:', error);
|
|
@@ -312,6 +319,7 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
|
|
|
312
319
|
sendMessage,
|
|
313
320
|
onMessage,
|
|
314
321
|
onScriptComplete,
|
|
322
|
+
transformSendPayload,
|
|
315
323
|
]);
|
|
316
324
|
const handlePromptSubmit = useCallback(async (message, attachments) => {
|
|
317
325
|
const chatId = currentChatId;
|
|
@@ -688,6 +696,16 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
|
|
|
688
696
|
}
|
|
689
697
|
setIsOpen(false);
|
|
690
698
|
}, [embedAsPage, shellChatPanelOpen, chatOpen, isOpen]);
|
|
699
|
+
/** Shell width reserved but no chat UI (e.g. nav dropped `?chat=` while old page still had it). */
|
|
700
|
+
useEffect(() => {
|
|
701
|
+
if (embedAsPage) {
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
if (!shouldCloseOrphanShellChat(shellChatPanelOpen, chatOpen, isOpen)) {
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
setChatPanelOpen(false);
|
|
708
|
+
}, [embedAsPage, shellChatPanelOpen, chatOpen, isOpen, setChatPanelOpen]);
|
|
691
709
|
/** Shell chat closed while `?chat=` still set (fallback if layout event was missed). */
|
|
692
710
|
useEffect(() => {
|
|
693
711
|
if (embedAsPage) {
|
|
@@ -706,15 +724,20 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
|
|
|
706
724
|
}
|
|
707
725
|
dismissChatForLayout();
|
|
708
726
|
}, [embedAsPage, shellChatPanelOpen, chatOpen, dismissChatForLayout, isOpen]);
|
|
709
|
-
/** Route change: release shell
|
|
727
|
+
/** Route change: release shell unless destination URL still has `?chat=` (read live search, not stale closure). */
|
|
710
728
|
useEffect(() => {
|
|
711
729
|
return () => {
|
|
712
|
-
if (
|
|
713
|
-
|
|
730
|
+
if (embedAsPage || !openedShellChatRef.current) {
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
openedShellChatRef.current = false;
|
|
734
|
+
const chatStillInUrl = typeof window !== 'undefined' &&
|
|
735
|
+
new URLSearchParams(window.location.search).has(CHAT_QUERY_PARAM);
|
|
736
|
+
if (!chatStillInUrl) {
|
|
714
737
|
setChatPanelOpen(false);
|
|
715
738
|
}
|
|
716
739
|
};
|
|
717
|
-
}, [embedAsPage,
|
|
740
|
+
}, [embedAsPage, setChatPanelOpen]);
|
|
718
741
|
useEffect(() => {
|
|
719
742
|
if (embedAsPage) {
|
|
720
743
|
return;
|
|
@@ -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)}}.Tooltip_tooltipContent__b3pS-{backdrop-filter:blur(10px);background-color:var(--popover);border:1px solid var(--border);border-radius:var(--p-3);box-shadow:0 10px 15px -3px rgba(0,0,0,.1);color:var(--popover-foreground);font-size:12px;min-width:100%;padding:6px 12px;text-wrap:balance;transform-origin:var(--radix-tooltip-content-transform-origin);width:-moz-fit-content;width:fit-content;word-break:break-word;z-index:50}.Tooltip_tooltipContent__b3pS-[data-state=delayed-open],.Tooltip_tooltipContent__b3pS-[data-state=instant-open],.Tooltip_tooltipContent__b3pS-[data-state=open]{animation:Tooltip_fade-in__ZQqZv .15s ease-out,Tooltip_zoom-in__SbWQb .15s ease-out}.Tooltip_tooltipContent__b3pS-[data-state=closed]{animation:Tooltip_fade-out__UOBET .1s ease-in,Tooltip_zoom-out__fodOk .1s ease-in}.Tooltip_tooltipContent__b3pS-[data-state=delayed-open][data-side=bottom],.Tooltip_tooltipContent__b3pS-[data-state=instant-open][data-side=bottom],.Tooltip_tooltipContent__b3pS-[data-state=open][data-side=bottom]{animation:Tooltip_fade-in__ZQqZv .15s ease-out,Tooltip_zoom-in__SbWQb .15s ease-out,Tooltip_slide-in-from-top-2__8uuS- .15s ease-out}.Tooltip_tooltipContent__b3pS-[data-state=delayed-open][data-side=left],.Tooltip_tooltipContent__b3pS-[data-state=instant-open][data-side=left],.Tooltip_tooltipContent__b3pS-[data-state=open][data-side=left]{animation:Tooltip_fade-in__ZQqZv .15s ease-out,Tooltip_zoom-in__SbWQb .15s ease-out,Tooltip_slide-in-from-right-2__Uu79F .15s ease-out}.Tooltip_tooltipContent__b3pS-[data-state=delayed-open][data-side=right],.Tooltip_tooltipContent__b3pS-[data-state=instant-open][data-side=right],.Tooltip_tooltipContent__b3pS-[data-state=open][data-side=right]{animation:Tooltip_fade-in__ZQqZv .15s ease-out,Tooltip_zoom-in__SbWQb .15s ease-out,Tooltip_slide-in-from-left-2__23kHm .15s ease-out}.Tooltip_tooltipContent__b3pS-[data-state=delayed-open][data-side=top],.Tooltip_tooltipContent__b3pS-[data-state=instant-open][data-side=top],.Tooltip_tooltipContent__b3pS-[data-state=open][data-side=top]{animation:Tooltip_fade-in__ZQqZv .15s ease-out,Tooltip_zoom-in__SbWQb .15s ease-out,Tooltip_slide-in-from-bottom-2__O-Aa8 .15s ease-out}.Tooltip_tooltipContent__b3pS-[data-state=closed][data-side=bottom],.Tooltip_tooltipContent__b3pS-[data-state=closed][data-side=left],.Tooltip_tooltipContent__b3pS-[data-state=closed][data-side=right],.Tooltip_tooltipContent__b3pS-[data-state=closed][data-side=top]{animation:Tooltip_fade-out__UOBET .1s ease-in,Tooltip_zoom-out__fodOk .1s ease-in}.Tooltip_tooltipContent__b3pS-.Tooltip_tooltipContentOverTrigger__VQAU3[data-state=delayed-open],.Tooltip_tooltipContent__b3pS-.Tooltip_tooltipContentOverTrigger__VQAU3[data-state=instant-open],.Tooltip_tooltipContent__b3pS-.Tooltip_tooltipContentOverTrigger__VQAU3[data-state=open]{animation:Tooltip_fade-in__ZQqZv .15s ease-out}.Tooltip_tooltipContent__b3pS-.Tooltip_tooltipContentOverTrigger__VQAU3[data-state=closed]{animation:Tooltip_fade-out__UOBET .1s ease-in}.
|
|
3
|
+
var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.Tooltip_tooltipContent__b3pS-{backdrop-filter:blur(10px);background-color:var(--popover);border:1px solid var(--border);border-radius:var(--p-3);box-shadow:0 10px 15px -3px rgba(0,0,0,.1);color:var(--popover-foreground);font-size:12px;min-width:100%;padding:6px 12px;text-wrap:balance;transform-origin:var(--radix-tooltip-content-transform-origin);width:-moz-fit-content;width:fit-content;word-break:break-word;z-index:50}.Tooltip_tooltipContent__b3pS-[data-state=delayed-open],.Tooltip_tooltipContent__b3pS-[data-state=instant-open],.Tooltip_tooltipContent__b3pS-[data-state=open]{animation:Tooltip_fade-in__ZQqZv .15s ease-out,Tooltip_zoom-in__SbWQb .15s ease-out}.Tooltip_tooltipContent__b3pS-[data-state=closed]{animation:Tooltip_fade-out__UOBET .1s ease-in,Tooltip_zoom-out__fodOk .1s ease-in}.Tooltip_tooltipContent__b3pS-[data-state=delayed-open][data-side=bottom],.Tooltip_tooltipContent__b3pS-[data-state=instant-open][data-side=bottom],.Tooltip_tooltipContent__b3pS-[data-state=open][data-side=bottom]{animation:Tooltip_fade-in__ZQqZv .15s ease-out,Tooltip_zoom-in__SbWQb .15s ease-out,Tooltip_slide-in-from-top-2__8uuS- .15s ease-out}.Tooltip_tooltipContent__b3pS-[data-state=delayed-open][data-side=left],.Tooltip_tooltipContent__b3pS-[data-state=instant-open][data-side=left],.Tooltip_tooltipContent__b3pS-[data-state=open][data-side=left]{animation:Tooltip_fade-in__ZQqZv .15s ease-out,Tooltip_zoom-in__SbWQb .15s ease-out,Tooltip_slide-in-from-right-2__Uu79F .15s ease-out}.Tooltip_tooltipContent__b3pS-[data-state=delayed-open][data-side=right],.Tooltip_tooltipContent__b3pS-[data-state=instant-open][data-side=right],.Tooltip_tooltipContent__b3pS-[data-state=open][data-side=right]{animation:Tooltip_fade-in__ZQqZv .15s ease-out,Tooltip_zoom-in__SbWQb .15s ease-out,Tooltip_slide-in-from-left-2__23kHm .15s ease-out}.Tooltip_tooltipContent__b3pS-[data-state=delayed-open][data-side=top],.Tooltip_tooltipContent__b3pS-[data-state=instant-open][data-side=top],.Tooltip_tooltipContent__b3pS-[data-state=open][data-side=top]{animation:Tooltip_fade-in__ZQqZv .15s ease-out,Tooltip_zoom-in__SbWQb .15s ease-out,Tooltip_slide-in-from-bottom-2__O-Aa8 .15s ease-out}.Tooltip_tooltipContent__b3pS-[data-state=closed][data-side=bottom],.Tooltip_tooltipContent__b3pS-[data-state=closed][data-side=left],.Tooltip_tooltipContent__b3pS-[data-state=closed][data-side=right],.Tooltip_tooltipContent__b3pS-[data-state=closed][data-side=top]{animation:Tooltip_fade-out__UOBET .1s ease-in,Tooltip_zoom-out__fodOk .1s ease-in}.Tooltip_tooltipContent__b3pS-.Tooltip_tooltipContentOverTrigger__VQAU3{box-sizing:border-box;height:auto;text-wrap:wrap}.Tooltip_tooltipContent__b3pS-.Tooltip_tooltipContentOverTrigger__VQAU3[data-state=delayed-open],.Tooltip_tooltipContent__b3pS-.Tooltip_tooltipContentOverTrigger__VQAU3[data-state=instant-open],.Tooltip_tooltipContent__b3pS-.Tooltip_tooltipContentOverTrigger__VQAU3[data-state=open]{animation:Tooltip_fade-in__ZQqZv .15s ease-out}.Tooltip_tooltipContent__b3pS-.Tooltip_tooltipContentOverTrigger__VQAU3[data-state=closed]{animation:Tooltip_fade-out__UOBET .1s ease-in}.Tooltip_tooltipArrow__87DVL{background-color:var(--popover);border-bottom:1px solid var(--border);border-left-width:1px;border-left:0 solid var(--border);border-radius:2px;border-right:1px solid var(--border);border-top-width:1px;border-top:0 solid var(--border);fill:var(--popover);height:10px;transform:translateY(calc(-50% + .5px)) rotate(45deg);width:10px;z-index:50}@keyframes Tooltip_fade-in__ZQqZv{0%{opacity:0}to{opacity:1}}@keyframes Tooltip_fade-out__UOBET{0%{opacity:1}to{opacity:0}}@keyframes Tooltip_zoom-in__SbWQb{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}@keyframes Tooltip_zoom-out__fodOk{0%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(.95)}}@keyframes Tooltip_slide-in-from-top-2__8uuS-{0%{opacity:0;transform:translateY(-.5rem)}to{opacity:1;transform:translateY(0)}}@keyframes Tooltip_slide-in-from-right-2__Uu79F{0%{opacity:0;transform:translateX(.5rem)}to{opacity:1;transform:translateX(0)}}@keyframes Tooltip_slide-in-from-left-2__23kHm{0%{opacity:0;transform:translateX(-.5rem)}to{opacity:1;transform:translateX(0)}}@keyframes Tooltip_slide-in-from-bottom-2__O-Aa8{0%{opacity:0;transform:translateY(.5rem)}to{opacity:1;transform:translateY(0)}}";
|
|
4
4
|
var S = {"tooltipContent":"Tooltip_tooltipContent__b3pS-","fade-in":"Tooltip_fade-in__ZQqZv","zoom-in":"Tooltip_zoom-in__SbWQb","fade-out":"Tooltip_fade-out__UOBET","zoom-out":"Tooltip_zoom-out__fodOk","slide-in-from-top-2":"Tooltip_slide-in-from-top-2__8uuS-","slide-in-from-right-2":"Tooltip_slide-in-from-right-2__Uu79F","slide-in-from-left-2":"Tooltip_slide-in-from-left-2__23kHm","slide-in-from-bottom-2":"Tooltip_slide-in-from-bottom-2__O-Aa8","tooltipContentOverTrigger":"Tooltip_tooltipContentOverTrigger__VQAU3","tooltipArrow":"Tooltip_tooltipArrow__87DVL"};
|
|
5
5
|
styleInject(css_248z);
|
|
6
6
|
|
|
@@ -12,7 +12,7 @@ import 'lucide-react';
|
|
|
12
12
|
import S from './SybilionAppHeader.styl.js';
|
|
13
13
|
|
|
14
14
|
function SybilionAppHeader({ pageHeaderId, actionsAnchorId = PAGE_HEADER_ACTIONS_ID, actionsAnchorClassName, actionsStart, actionsEnd, pathname, onNavigate, authenticated, defaultApps, appsStorageKey, logo, logoAreaClassName, welcomeBannerOffset, ...navUserHeaderProps }) {
|
|
15
|
-
return (jsxs(AppHeaderPortal, { pageHeaderId: pageHeaderId, children: [jsx("div", { className: cn(S.logoArea, welcomeBannerOffset && S.logoAreaWithBanner, logoAreaClassName), children: jsx(Link, { to: "/", className: S.logoLink, children: logo ?? jsx(Logo, { size: "md", "aria-hidden": true }) }) }), jsx(WorkspaceAppSwitcher, { pathname: pathname, onNavigate: onNavigate, authenticated: authenticated, defaultApps: defaultApps, appsStorageKey: appsStorageKey }), jsx(Gap, {}), jsxs("div", {
|
|
15
|
+
return (jsxs(AppHeaderPortal, { pageHeaderId: pageHeaderId, children: [jsx("div", { className: cn(S.logoArea, welcomeBannerOffset && S.logoAreaWithBanner, logoAreaClassName), children: jsx(Link, { to: "/", className: S.logoLink, children: logo ?? jsx(Logo, { size: "md", "aria-hidden": true }) }) }), jsx(WorkspaceAppSwitcher, { pathname: pathname, onNavigate: onNavigate, authenticated: authenticated, defaultApps: defaultApps, appsStorageKey: appsStorageKey }), jsx(Gap, {}), jsxs("div", { className: cn(S.actionsAnchor, actionsAnchorClassName), children: [jsx("div", { id: actionsAnchorId, className: S.pageActionsPortal }), actionsStart, jsx(NavUserHeader, { ...navUserHeaderProps }), actionsEnd] })] }));
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export { SybilionAppHeader };
|
|
@@ -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)}}.SybilionAppHeader_actionsAnchor__ress2{align-items:center;display:flex;flex-shrink:0;gap:var(--p-4)}.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)}";
|
|
4
|
-
var S = {"actionsAnchor":"SybilionAppHeader_actionsAnchor__ress2","logoArea":"SybilionAppHeader_logoArea__3HAhG","logoAreaWithBanner":"SybilionAppHeader_logoAreaWithBanner__7Iy78","logoLink":"SybilionAppHeader_logoLink__bH-KX"};
|
|
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)}";
|
|
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
|
|
|
7
7
|
export { S as default };
|
|
@@ -270,6 +270,23 @@ function ChatProvider({ children, userSwitchKey, sendChatMessage: sendChatMessag
|
|
|
270
270
|
return { ...prev, [scopeId]: updatedChats };
|
|
271
271
|
});
|
|
272
272
|
}, [userSwitchKey]);
|
|
273
|
+
const setChatMessages = useCallback((scopeId, chatId, messages) => {
|
|
274
|
+
if (userSwitchKey === null)
|
|
275
|
+
return;
|
|
276
|
+
addScopeIdToRegistry(scopeId);
|
|
277
|
+
const cloned = messages.map(message => ({ ...message }));
|
|
278
|
+
setChats(prev => {
|
|
279
|
+
const scopeChats = prev[scopeId] ?? [];
|
|
280
|
+
const updatedChats = scopeChats.map(chat => {
|
|
281
|
+
if (chat.session_id !== chatId)
|
|
282
|
+
return chat;
|
|
283
|
+
return { ...chat, messages: cloned };
|
|
284
|
+
});
|
|
285
|
+
const chatsKey = getChatsKey(scopeId);
|
|
286
|
+
LS.set(chatsKey, updatedChats);
|
|
287
|
+
return { ...prev, [scopeId]: updatedChats };
|
|
288
|
+
});
|
|
289
|
+
}, [userSwitchKey]);
|
|
273
290
|
const sendMessage = useCallback(async (scopeId, message, chatId) => {
|
|
274
291
|
const targetChatId = chatId ?? getCurrentChatId(scopeId);
|
|
275
292
|
if (targetChatId === null || targetChatId === '') {
|
|
@@ -347,6 +364,7 @@ function ChatProvider({ children, userSwitchKey, sendChatMessage: sendChatMessag
|
|
|
347
364
|
addMessage,
|
|
348
365
|
removeMessageById,
|
|
349
366
|
updateMessageById,
|
|
367
|
+
setChatMessages,
|
|
350
368
|
sendMessage,
|
|
351
369
|
getChatsForScopeId,
|
|
352
370
|
getCurrentChatId,
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
export declare function shouldOpenChatFromUrl(chatOpen: boolean, layoutDismissed: boolean): boolean;
|
|
3
3
|
export declare function shouldHealChatShellDesync(chatOpen: boolean, shellChatPanelOpen: boolean, layoutDismissed: boolean, isOpen: boolean): boolean;
|
|
4
4
|
export declare function shouldCloseStaleLocalChatOpen(shellChatPanelOpen: boolean, chatOpen: boolean, isOpen: boolean): boolean;
|
|
5
|
+
/** Shell still reserves width but no instance is showing chat (e.g. route change dropped `?chat=`). */
|
|
6
|
+
export declare function shouldCloseOrphanShellChat(shellChatPanelOpen: boolean, chatOpen: boolean, isOpen: boolean): boolean;
|
|
7
|
+
/** App shell: collapse chat column when URL has no `?chat=` (no mounted ChatSheet required). */
|
|
8
|
+
export declare function shouldCollapseShellChatWithoutUrlParam(hasChatUrlParam: boolean): boolean;
|
|
5
9
|
export declare function shouldDismissChatAfterShellClosed(params: {
|
|
6
10
|
shellChatPanelOpen: boolean;
|
|
7
11
|
wasShellChatPanelOpen: boolean;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
|
-
import { type Chat, type ChatSendMessagePayload, MessageRole, type UserTextFileAttachment } from '#uilib/components/ui/Chat/Chat.types';
|
|
2
|
+
import { type Chat, type ChatSendMessagePayload, type Message, MessageRole, type UserTextFileAttachment } from '#uilib/components/ui/Chat/Chat.types';
|
|
3
3
|
import type { ChatResponse } from '#uilib/types/chat-api.types';
|
|
4
4
|
export type SendChatMessageFn = (message: string, targetChatId: string) => Promise<ChatResponse>;
|
|
5
5
|
export type { ChatSendMessagePayload, UserTextFileAttachment, } from '#uilib/components/ui/Chat/Chat.types';
|
|
@@ -20,6 +20,8 @@ export interface ChatContextType {
|
|
|
20
20
|
addMessage: (scopeId: string, chatId: string, role: MessageRole, text: string, options?: AddChatMessageOptions) => string | undefined;
|
|
21
21
|
removeMessageById: (scopeId: string, chatId: string, messageId: string) => void;
|
|
22
22
|
updateMessageById: (scopeId: string, chatId: string, messageId: string, patch: UpdateChatMessagePatch) => void;
|
|
23
|
+
/** Replaces all messages on a session (e.g. seeding from another chat). */
|
|
24
|
+
setChatMessages: (scopeId: string, chatId: string, messages: Message[]) => void;
|
|
23
25
|
sendMessage: (scopeId: string, message: string | ChatSendMessagePayload, chatId?: string) => Promise<string>;
|
|
24
26
|
getChatsForScopeId: (scopeId: string) => Chat[];
|
|
25
27
|
getCurrentChatId: (scopeId: string) => string | null;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function ChatComposerPrefillPage(): import("react/jsx-runtime").JSX.Element;
|
package/package.json
CHANGED
|
@@ -208,8 +208,11 @@ export function useChatPromptEditor({
|
|
|
208
208
|
queueMicrotask(() => {
|
|
209
209
|
const dom = chatPromptSafeEditorDom(editor);
|
|
210
210
|
if (dom) syncChatPromptComposerHeight(dom, t);
|
|
211
|
+
if (!disabled && !editor.isDestroyed) {
|
|
212
|
+
editor.chain().focus('end').run();
|
|
213
|
+
}
|
|
211
214
|
});
|
|
212
|
-
}, [editor, prefillMessage]);
|
|
215
|
+
}, [editor, prefillMessage, disabled]);
|
|
213
216
|
|
|
214
217
|
useLayoutEffect(() => {
|
|
215
218
|
if (!editor) return;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isChatPanelVisible,
|
|
3
|
+
shouldCloseOrphanShellChat,
|
|
3
4
|
shouldCloseStaleLocalChatOpen,
|
|
5
|
+
shouldCollapseShellChatWithoutUrlParam,
|
|
4
6
|
shouldDismissChatAfterShellClosed,
|
|
5
7
|
shouldHealChatShellDesync,
|
|
6
8
|
shouldOpenChatFromUrl,
|
|
@@ -36,6 +38,30 @@ describe('shouldCloseStaleLocalChatOpen', () => {
|
|
|
36
38
|
});
|
|
37
39
|
});
|
|
38
40
|
|
|
41
|
+
describe('shouldCollapseShellChatWithoutUrlParam', () => {
|
|
42
|
+
it('collapses when chat param absent', () => {
|
|
43
|
+
expect(shouldCollapseShellChatWithoutUrlParam(false)).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('keeps shell when chat param present', () => {
|
|
47
|
+
expect(shouldCollapseShellChatWithoutUrlParam(true)).toBe(false);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('shouldCloseOrphanShellChat', () => {
|
|
52
|
+
it('closes shell slot when URL and local UI are both closed', () => {
|
|
53
|
+
expect(shouldCloseOrphanShellChat(true, false, false)).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('keeps shell while ?chat= requests panel', () => {
|
|
57
|
+
expect(shouldCloseOrphanShellChat(true, true, false)).toBe(false);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('keeps shell while this instance still renders chat', () => {
|
|
61
|
+
expect(shouldCloseOrphanShellChat(true, false, true)).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
39
65
|
describe('shouldDismissChatAfterShellClosed', () => {
|
|
40
66
|
it('dismisses when shell closes with ?chat= and this instance opened chat', () => {
|
|
41
67
|
expect(
|
|
@@ -30,6 +30,22 @@ export function shouldCloseStaleLocalChatOpen(
|
|
|
30
30
|
return !shellChatPanelOpen && !chatOpen && isOpen;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/** Shell still reserves width but no instance is showing chat (e.g. route change dropped `?chat=`). */
|
|
34
|
+
export function shouldCloseOrphanShellChat(
|
|
35
|
+
shellChatPanelOpen: boolean,
|
|
36
|
+
chatOpen: boolean,
|
|
37
|
+
isOpen: boolean,
|
|
38
|
+
): boolean {
|
|
39
|
+
return shellChatPanelOpen && !chatOpen && !isOpen;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** App shell: collapse chat column when URL has no `?chat=` (no mounted ChatSheet required). */
|
|
43
|
+
export function shouldCollapseShellChatWithoutUrlParam(
|
|
44
|
+
hasChatUrlParam: boolean,
|
|
45
|
+
): boolean {
|
|
46
|
+
return !hasChatUrlParam;
|
|
47
|
+
}
|
|
48
|
+
|
|
33
49
|
export function shouldDismissChatAfterShellClosed(params: {
|
|
34
50
|
shellChatPanelOpen: boolean;
|
|
35
51
|
wasShellChatPanelOpen: boolean;
|
|
@@ -54,6 +54,7 @@ import type { ChatEmptyStateConfig } from '../ChatEmptyState/ChatEmptyState.type
|
|
|
54
54
|
import type { ChatEmptyStateProps } from '../ChatEmptyState/ChatEmptyState.types';
|
|
55
55
|
import {
|
|
56
56
|
isChatPanelVisible,
|
|
57
|
+
shouldCloseOrphanShellChat,
|
|
57
58
|
shouldCloseStaleLocalChatOpen,
|
|
58
59
|
shouldDismissChatAfterShellClosed,
|
|
59
60
|
shouldHealChatShellDesync,
|
|
@@ -197,16 +198,20 @@ export function useChatPanelChromeModel({
|
|
|
197
198
|
const layoutDismissedRef = useRef(false);
|
|
198
199
|
const panelActive = embedAsPage || isOpen;
|
|
199
200
|
|
|
200
|
-
// Ensure
|
|
201
|
+
// Ensure a renderable session when the panel is active (pick existing or create).
|
|
201
202
|
useEffect(() => {
|
|
202
|
-
if (panelActive
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
203
|
+
if (!panelActive) return;
|
|
204
|
+
|
|
205
|
+
const currentValid =
|
|
206
|
+
currentChatId != null &&
|
|
207
|
+
currentChatId !== '' &&
|
|
208
|
+
chats.some(chat => chat.session_id === currentChatId);
|
|
209
|
+
if (currentValid) return;
|
|
210
|
+
|
|
211
|
+
if (chats.length > 0) {
|
|
212
|
+
setCurrentChatId(chats[0].session_id);
|
|
213
|
+
} else {
|
|
214
|
+
newChat();
|
|
210
215
|
}
|
|
211
216
|
}, [panelActive, currentChatId, chats, setCurrentChatId, newChat]);
|
|
212
217
|
const [scriptByChatId, setScriptByChatId] = useState<
|
|
@@ -496,8 +501,16 @@ export function useChatPanelChromeModel({
|
|
|
496
501
|
endLocalDemoFlow(chatId);
|
|
497
502
|
void (async () => {
|
|
498
503
|
try {
|
|
499
|
-
|
|
500
|
-
|
|
504
|
+
let payload: string | ChatSendMessagePayload = displayLabel;
|
|
505
|
+
if (transformSendPayload) {
|
|
506
|
+
payload = await transformSendPayload(
|
|
507
|
+
displayLabel,
|
|
508
|
+
undefined,
|
|
509
|
+
displayLabel,
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
const assistantResponse = await sendMessage(payload);
|
|
513
|
+
onMessage?.(displayTextFromSendPayload(payload), assistantResponse);
|
|
501
514
|
} catch (error) {
|
|
502
515
|
logger.error('Error sending chat message:', error);
|
|
503
516
|
}
|
|
@@ -514,6 +527,7 @@ export function useChatPanelChromeModel({
|
|
|
514
527
|
sendMessage,
|
|
515
528
|
onMessage,
|
|
516
529
|
onScriptComplete,
|
|
530
|
+
transformSendPayload,
|
|
517
531
|
],
|
|
518
532
|
);
|
|
519
533
|
|
|
@@ -936,6 +950,17 @@ export function useChatPanelChromeModel({
|
|
|
936
950
|
setIsOpen(false);
|
|
937
951
|
}, [embedAsPage, shellChatPanelOpen, chatOpen, isOpen]);
|
|
938
952
|
|
|
953
|
+
/** Shell width reserved but no chat UI (e.g. nav dropped `?chat=` while old page still had it). */
|
|
954
|
+
useEffect(() => {
|
|
955
|
+
if (embedAsPage) {
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
if (!shouldCloseOrphanShellChat(shellChatPanelOpen, chatOpen, isOpen)) {
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
setChatPanelOpen(false);
|
|
962
|
+
}, [embedAsPage, shellChatPanelOpen, chatOpen, isOpen, setChatPanelOpen]);
|
|
963
|
+
|
|
939
964
|
/** Shell chat closed while `?chat=` still set (fallback if layout event was missed). */
|
|
940
965
|
useEffect(() => {
|
|
941
966
|
if (embedAsPage) {
|
|
@@ -957,15 +982,21 @@ export function useChatPanelChromeModel({
|
|
|
957
982
|
dismissChatForLayout();
|
|
958
983
|
}, [embedAsPage, shellChatPanelOpen, chatOpen, dismissChatForLayout, isOpen]);
|
|
959
984
|
|
|
960
|
-
/** Route change: release shell
|
|
985
|
+
/** Route change: release shell unless destination URL still has `?chat=` (read live search, not stale closure). */
|
|
961
986
|
useEffect(() => {
|
|
962
987
|
return () => {
|
|
963
|
-
if (
|
|
964
|
-
|
|
988
|
+
if (embedAsPage || !openedShellChatRef.current) {
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
openedShellChatRef.current = false;
|
|
992
|
+
const chatStillInUrl =
|
|
993
|
+
typeof window !== 'undefined' &&
|
|
994
|
+
new URLSearchParams(window.location.search).has(CHAT_QUERY_PARAM);
|
|
995
|
+
if (!chatStillInUrl) {
|
|
965
996
|
setChatPanelOpen(false);
|
|
966
997
|
}
|
|
967
998
|
};
|
|
968
|
-
}, [embedAsPage,
|
|
999
|
+
}, [embedAsPage, setChatPanelOpen]);
|
|
969
1000
|
|
|
970
1001
|
useEffect(() => {
|
|
971
1002
|
if (embedAsPage) {
|
|
@@ -59,6 +59,10 @@
|
|
|
59
59
|
animation fade-out 0.1s ease-in, zoom-out 0.1s ease-in
|
|
60
60
|
|
|
61
61
|
&.tooltipContentOverTrigger
|
|
62
|
+
box-sizing border-box
|
|
63
|
+
height auto
|
|
64
|
+
text-wrap initial
|
|
65
|
+
|
|
62
66
|
&[data-state="open"],
|
|
63
67
|
&[data-state="instant-open"],
|
|
64
68
|
&[data-state="delayed-open"]
|
|
@@ -67,10 +71,6 @@
|
|
|
67
71
|
&[data-state="closed"]
|
|
68
72
|
animation fade-out 0.1s ease-in
|
|
69
73
|
|
|
70
|
-
.tooltipContentOverTrigger
|
|
71
|
-
box-sizing border-box
|
|
72
|
-
height auto
|
|
73
|
-
|
|
74
74
|
.tooltipArrow
|
|
75
75
|
z-index 50
|
|
76
76
|
width 10px
|
|
@@ -69,10 +69,8 @@ export function SybilionAppHeader({
|
|
|
69
69
|
appsStorageKey={appsStorageKey}
|
|
70
70
|
/>
|
|
71
71
|
<Gap />
|
|
72
|
-
<div
|
|
73
|
-
id={actionsAnchorId}
|
|
74
|
-
className={cn(S.actionsAnchor, actionsAnchorClassName)}
|
|
75
|
-
>
|
|
72
|
+
<div className={cn(S.actionsAnchor, actionsAnchorClassName)}>
|
|
73
|
+
<div id={actionsAnchorId} className={S.pageActionsPortal} />
|
|
76
74
|
{actionsStart}
|
|
77
75
|
<NavUserHeader {...navUserHeaderProps} />
|
|
78
76
|
{actionsEnd}
|
|
@@ -67,6 +67,12 @@ export interface ChatContextType {
|
|
|
67
67
|
messageId: string,
|
|
68
68
|
patch: UpdateChatMessagePatch,
|
|
69
69
|
) => void;
|
|
70
|
+
/** Replaces all messages on a session (e.g. seeding from another chat). */
|
|
71
|
+
setChatMessages: (
|
|
72
|
+
scopeId: string,
|
|
73
|
+
chatId: string,
|
|
74
|
+
messages: Message[],
|
|
75
|
+
) => void;
|
|
70
76
|
sendMessage: (
|
|
71
77
|
scopeId: string,
|
|
72
78
|
message: string | ChatSendMessagePayload,
|
|
@@ -425,6 +431,28 @@ export function ChatProvider({
|
|
|
425
431
|
[userSwitchKey],
|
|
426
432
|
);
|
|
427
433
|
|
|
434
|
+
const setChatMessages = useCallback(
|
|
435
|
+
(scopeId: string, chatId: string, messages: Message[]) => {
|
|
436
|
+
if (userSwitchKey === null) return;
|
|
437
|
+
addScopeIdToRegistry(scopeId);
|
|
438
|
+
const cloned = messages.map(message => ({ ...message }));
|
|
439
|
+
|
|
440
|
+
setChats(prev => {
|
|
441
|
+
const scopeChats = prev[scopeId] ?? [];
|
|
442
|
+
const updatedChats = scopeChats.map(chat => {
|
|
443
|
+
if (chat.session_id !== chatId) return chat;
|
|
444
|
+
return { ...chat, messages: cloned };
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
const chatsKey = getChatsKey(scopeId);
|
|
448
|
+
LS.set(chatsKey, updatedChats);
|
|
449
|
+
|
|
450
|
+
return { ...prev, [scopeId]: updatedChats };
|
|
451
|
+
});
|
|
452
|
+
},
|
|
453
|
+
[userSwitchKey],
|
|
454
|
+
);
|
|
455
|
+
|
|
428
456
|
const sendMessage = useCallback(
|
|
429
457
|
async (
|
|
430
458
|
scopeId: string,
|
|
@@ -543,6 +571,7 @@ export function ChatProvider({
|
|
|
543
571
|
addMessage,
|
|
544
572
|
removeMessageById,
|
|
545
573
|
updateMessageById,
|
|
574
|
+
setChatMessages,
|
|
546
575
|
sendMessage,
|
|
547
576
|
getChatsForScopeId,
|
|
548
577
|
getCurrentChatId,
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { Button } from '#uilib/components/ui/Button';
|
|
4
|
+
import {
|
|
5
|
+
ChatChrome,
|
|
6
|
+
type ChatSheetActions,
|
|
7
|
+
type Message,
|
|
8
|
+
MessageRole,
|
|
9
|
+
} from '#uilib/components/ui/Chat';
|
|
10
|
+
import { PageContentSection } from '#uilib/components/ui/Page';
|
|
11
|
+
import { ScrollRef } from '@homecode/ui';
|
|
12
|
+
import { MessageSquare } from 'lucide-react';
|
|
13
|
+
|
|
14
|
+
import { AppPageHeader } from '../components/AppPageHeader/AppPageHeader';
|
|
15
|
+
import { DOCS_CHAT_USER_KEY } from '../docsConstants';
|
|
16
|
+
import { DocsHeaderActions } from '../docsHeaderActions';
|
|
17
|
+
|
|
18
|
+
const NO_QUICK_REPLY_KEYS: ReadonlySet<string> = new Set();
|
|
19
|
+
|
|
20
|
+
const SAMPLE_PREFILL =
|
|
21
|
+
'Summarize revenue trends for Q4 and highlight outliers.';
|
|
22
|
+
|
|
23
|
+
const ASSISTANT_REPLY_TEXT =
|
|
24
|
+
'Prefill demo reply — composer should stay editable after send.';
|
|
25
|
+
|
|
26
|
+
function makeMessage(role: MessageRole, text: string): Message {
|
|
27
|
+
return {
|
|
28
|
+
id: crypto.randomUUID(),
|
|
29
|
+
role,
|
|
30
|
+
text,
|
|
31
|
+
timestamp: Date.now(),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const DOCS_CHAT_PREFILL_SCOPE_ID = `${DOCS_CHAT_USER_KEY}-docs-composer-prefill`;
|
|
36
|
+
|
|
37
|
+
export default function ChatComposerPrefillPage() {
|
|
38
|
+
const [messages, setMessages] = useState<Message[]>([]);
|
|
39
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
40
|
+
const [inlinePrefill, setInlinePrefill] = useState<string | null>(null);
|
|
41
|
+
const replyTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
42
|
+
const scrollRef = useRef<ScrollRef>(null);
|
|
43
|
+
const sheetActionsRef = useRef<ChatSheetActions | null>(null);
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
return () => {
|
|
47
|
+
if (replyTimeoutRef.current != null) {
|
|
48
|
+
clearTimeout(replyTimeoutRef.current);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}, []);
|
|
52
|
+
|
|
53
|
+
const isEmpty = messages.length === 0 && !isLoading;
|
|
54
|
+
const isLastMessageFromUser =
|
|
55
|
+
messages.length > 0 &&
|
|
56
|
+
messages[messages.length - 1]?.role === MessageRole.USER;
|
|
57
|
+
|
|
58
|
+
const onSubmit = useCallback(
|
|
59
|
+
(raw: string) => {
|
|
60
|
+
const text = raw.trim();
|
|
61
|
+
if (!text || isLoading) return;
|
|
62
|
+
|
|
63
|
+
setMessages(prev => [...prev, makeMessage(MessageRole.USER, text)]);
|
|
64
|
+
setIsLoading(true);
|
|
65
|
+
setInlinePrefill(null);
|
|
66
|
+
|
|
67
|
+
if (replyTimeoutRef.current != null) {
|
|
68
|
+
clearTimeout(replyTimeoutRef.current);
|
|
69
|
+
}
|
|
70
|
+
replyTimeoutRef.current = setTimeout(() => {
|
|
71
|
+
replyTimeoutRef.current = null;
|
|
72
|
+
setMessages(prev => [
|
|
73
|
+
...prev,
|
|
74
|
+
makeMessage(MessageRole.ASSISTANT, ASSISTANT_REPLY_TEXT),
|
|
75
|
+
]);
|
|
76
|
+
setIsLoading(false);
|
|
77
|
+
}, 900);
|
|
78
|
+
},
|
|
79
|
+
[isLoading],
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const prefillInlineComposer = useCallback(() => {
|
|
83
|
+
setInlinePrefill(SAMPLE_PREFILL);
|
|
84
|
+
}, []);
|
|
85
|
+
|
|
86
|
+
const openSheetWithPrefill = useCallback(() => {
|
|
87
|
+
sheetActionsRef.current?.openNewChatWithPrefill(SAMPLE_PREFILL);
|
|
88
|
+
}, []);
|
|
89
|
+
|
|
90
|
+
const promptDeepLink = `/docs/chat-composer-prefill?prompt=${encodeURIComponent(SAMPLE_PREFILL)}`;
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<>
|
|
94
|
+
<AppPageHeader
|
|
95
|
+
breadcrumbs={[{ label: 'Chat' }, { label: 'Composer prefill' }]}
|
|
96
|
+
title="Chat — composer prefill & focus"
|
|
97
|
+
subheader="Prefill the TipTap composer via prefillMessage, ChatSheetActions.openNewChatWithPrefill, or a ?prompt= deep link. Cursor lands at end; message is not auto-sent."
|
|
98
|
+
actions={
|
|
99
|
+
<DocsHeaderActions
|
|
100
|
+
actionsRef={sheetActionsRef}
|
|
101
|
+
scopeId={DOCS_CHAT_PREFILL_SCOPE_ID}
|
|
102
|
+
triggerLabel={<MessageSquare size={20} />}
|
|
103
|
+
triggerAriaLabel="AI Assistant"
|
|
104
|
+
emptyState={{
|
|
105
|
+
title: 'Start a conversation',
|
|
106
|
+
description:
|
|
107
|
+
'Use the buttons below or ?prompt= to pre-fill this composer.',
|
|
108
|
+
}}
|
|
109
|
+
/>
|
|
110
|
+
}
|
|
111
|
+
/>
|
|
112
|
+
<PageContentSection>
|
|
113
|
+
<p style={{ marginBottom: 12, fontSize: 14, lineHeight: 1.5 }}>
|
|
114
|
+
After prefill, the composer receives focus at the end of the text so
|
|
115
|
+
users can edit before sending. Wired through{' '}
|
|
116
|
+
<code style={{ fontSize: 13 }}>useChatPromptEditor</code> (
|
|
117
|
+
<code style={{ fontSize: 13 }}>prefillMessage</code>) and{' '}
|
|
118
|
+
<code style={{ fontSize: 13 }}>useChatPanelChromeModel</code> (
|
|
119
|
+
<code style={{ fontSize: 13 }}>promptPrefill</code> /{' '}
|
|
120
|
+
<code style={{ fontSize: 13 }}>openNewChatWithPrefill</code>).
|
|
121
|
+
</p>
|
|
122
|
+
<p
|
|
123
|
+
style={{
|
|
124
|
+
marginBottom: 16,
|
|
125
|
+
display: 'flex',
|
|
126
|
+
flexWrap: 'wrap',
|
|
127
|
+
gap: 8,
|
|
128
|
+
}}
|
|
129
|
+
>
|
|
130
|
+
<Button
|
|
131
|
+
type="button"
|
|
132
|
+
variant="outline"
|
|
133
|
+
size="sm"
|
|
134
|
+
onClick={prefillInlineComposer}
|
|
135
|
+
>
|
|
136
|
+
Prefill inline composer
|
|
137
|
+
</Button>
|
|
138
|
+
<Button
|
|
139
|
+
type="button"
|
|
140
|
+
variant="outline"
|
|
141
|
+
size="sm"
|
|
142
|
+
onClick={openSheetWithPrefill}
|
|
143
|
+
>
|
|
144
|
+
Open portal chat with prefill
|
|
145
|
+
</Button>
|
|
146
|
+
<Button type="button" variant="outline" size="sm" asChild>
|
|
147
|
+
<a href={promptDeepLink}>Try ?prompt= deep link</a>
|
|
148
|
+
</Button>
|
|
149
|
+
</p>
|
|
150
|
+
<h3 style={{ marginBottom: 8, fontSize: 14, fontWeight: 600 }}>
|
|
151
|
+
What to verify
|
|
152
|
+
</h3>
|
|
153
|
+
<ul
|
|
154
|
+
style={{
|
|
155
|
+
margin: '0 0 16px',
|
|
156
|
+
paddingLeft: 20,
|
|
157
|
+
fontSize: 14,
|
|
158
|
+
lineHeight: 1.6,
|
|
159
|
+
}}
|
|
160
|
+
>
|
|
161
|
+
<li>Composer shows sample text; caret at end (not start)</li>
|
|
162
|
+
<li>User can type or edit before Enter / submit</li>
|
|
163
|
+
<li>Prefill does not send until user submits</li>
|
|
164
|
+
<li>
|
|
165
|
+
Portal path: new session opens, panel visible, same focus behavior
|
|
166
|
+
</li>
|
|
167
|
+
<li>
|
|
168
|
+
Deep link strips <code style={{ fontSize: 13 }}>?prompt=</code> and
|
|
169
|
+
opens chat with prefill once
|
|
170
|
+
</li>
|
|
171
|
+
</ul>
|
|
172
|
+
<ChatChrome
|
|
173
|
+
showResizeHandle={false}
|
|
174
|
+
resizeHandle={undefined}
|
|
175
|
+
onClose={undefined}
|
|
176
|
+
isEmpty={isEmpty}
|
|
177
|
+
renderPresets={() => null}
|
|
178
|
+
messages={messages}
|
|
179
|
+
onQuickReply={() => {}}
|
|
180
|
+
suppressedQuickReplyKeys={NO_QUICK_REPLY_KEYS}
|
|
181
|
+
isLoading={isLoading}
|
|
182
|
+
scriptContinueLabel={undefined}
|
|
183
|
+
onScriptContinue={undefined}
|
|
184
|
+
showSyntheticBranchButtons={false}
|
|
185
|
+
unusedBranchKeys={[]}
|
|
186
|
+
showInlinePresets={false}
|
|
187
|
+
isLastMessageFromUser={isLastMessageFromUser}
|
|
188
|
+
scrollRef={scrollRef}
|
|
189
|
+
effectiveScopeId="docs-chat-composer-prefill-inline"
|
|
190
|
+
onPromptSubmit={onSubmit}
|
|
191
|
+
onChatDeleted={() => {}}
|
|
192
|
+
promptPrefill={inlinePrefill}
|
|
193
|
+
emptyState={{
|
|
194
|
+
title: 'Inline prefill demo',
|
|
195
|
+
description:
|
|
196
|
+
'Click “Prefill inline composer” or type below. Portal and ?prompt= use the header ChatSheet.',
|
|
197
|
+
}}
|
|
198
|
+
/>
|
|
199
|
+
</PageContentSection>
|
|
200
|
+
</>
|
|
201
|
+
);
|
|
202
|
+
}
|
package/src/docs/registry.ts
CHANGED
|
@@ -139,6 +139,12 @@ export const DOC_REGISTRY: DocEntry[] = [
|
|
|
139
139
|
section: 'Chat',
|
|
140
140
|
load: () => import('./pages/ChatAttachmentsDropzonePage'),
|
|
141
141
|
},
|
|
142
|
+
{
|
|
143
|
+
slug: 'chat-composer-prefill',
|
|
144
|
+
title: 'Chat composer prefill',
|
|
145
|
+
section: 'Chat',
|
|
146
|
+
load: () => import('./pages/ChatComposerPrefillPage'),
|
|
147
|
+
},
|
|
142
148
|
{
|
|
143
149
|
slug: 'checkbox',
|
|
144
150
|
title: 'Checkbox',
|