@sybilion/uilib 1.3.72 → 1.3.73
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/Chat.js +2 -2
- package/dist/esm/components/ui/Chat/ChatChrome/ChatChrome.js +2 -2
- package/dist/esm/components/ui/Chat/ChatSheet/ChatSelector.js +7 -2
- package/dist/esm/components/ui/Chat/ChatSheet/useChatPanelChromeModel.js +41 -9
- package/dist/esm/contexts/chat-context.js +19 -3
- 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 +2 -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 +2 -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/contexts/chat-context.d.ts +7 -3
- package/package.json +1 -1
- package/src/components/ui/Chat/Chat.tsx +6 -1
- package/src/components/ui/Chat/Chat.types.ts +2 -0
- package/src/components/ui/Chat/ChatChrome/ChatChrome.tsx +2 -0
- package/src/components/ui/Chat/ChatChrome/ChatChrome.types.ts +2 -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 +50 -8
- package/src/contexts/chat-context.tsx +29 -4
|
@@ -7,8 +7,8 @@ import { ChatMessage } from './ChatMessage/ChatMessage.js';
|
|
|
7
7
|
import { ChatPrompt } from './ChatPrompt/ChatPrompt.js';
|
|
8
8
|
import { ChatSelector } from './ChatSheet/ChatSelector.js';
|
|
9
9
|
|
|
10
|
-
function Chat({ children, className, isEmpty, scopeId, onChatDeleted, ...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, 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 }
|
|
@@ -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);
|
|
@@ -18,11 +18,11 @@ 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();
|
|
@@ -158,8 +158,39 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
|
|
|
158
158
|
}
|
|
159
159
|
};
|
|
160
160
|
const isEmpty = isChatEmpty(chat) && !isLoading;
|
|
161
|
-
const
|
|
161
|
+
const startEmptyNewChat = useCallback(() => {
|
|
162
|
+
const priorChatId = currentChatId;
|
|
162
163
|
const sessionId = newChat();
|
|
164
|
+
if (sessionId && priorChatId) {
|
|
165
|
+
endLocalDemoFlow(priorChatId);
|
|
166
|
+
}
|
|
167
|
+
if (sessionId) {
|
|
168
|
+
setPromptLinkPrefill(null);
|
|
169
|
+
}
|
|
170
|
+
return sessionId;
|
|
171
|
+
}, [newChat, currentChatId, endLocalDemoFlow]);
|
|
172
|
+
const startNewChatFromSelector = useCallback(() => {
|
|
173
|
+
const priorChatId = currentChatId;
|
|
174
|
+
const messages = chat?.messages ?? [];
|
|
175
|
+
const sessionId = newChat(copyHistoryOnNewChat && messages.length > 0
|
|
176
|
+
? { seedMessages: messages }
|
|
177
|
+
: undefined);
|
|
178
|
+
if (sessionId && priorChatId) {
|
|
179
|
+
endLocalDemoFlow(priorChatId);
|
|
180
|
+
}
|
|
181
|
+
if (sessionId) {
|
|
182
|
+
setPromptLinkPrefill(null);
|
|
183
|
+
}
|
|
184
|
+
return sessionId;
|
|
185
|
+
}, [
|
|
186
|
+
newChat,
|
|
187
|
+
copyHistoryOnNewChat,
|
|
188
|
+
chat?.messages,
|
|
189
|
+
currentChatId,
|
|
190
|
+
endLocalDemoFlow,
|
|
191
|
+
]);
|
|
192
|
+
const openNewChatWithPrefill = useCallback((prompt) => {
|
|
193
|
+
const sessionId = startEmptyNewChat();
|
|
163
194
|
if (sessionId == null) {
|
|
164
195
|
logger.warn('Chat prefill: sign in to use the assistant.');
|
|
165
196
|
return;
|
|
@@ -167,7 +198,7 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
|
|
|
167
198
|
const trimmed = prompt.trim();
|
|
168
199
|
setPromptLinkPrefill(trimmed || null);
|
|
169
200
|
onOpenChange(true);
|
|
170
|
-
}, [
|
|
201
|
+
}, [startEmptyNewChat, onOpenChange]);
|
|
171
202
|
/**
|
|
172
203
|
* App link: `?prompt=…` — open panel, pre-fill composer, strip param (read once on mount
|
|
173
204
|
* from `location.search`). If the selected session already has messages, `newChat()` first.
|
|
@@ -193,7 +224,7 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
|
|
|
193
224
|
promptParamHandledInEffectRef.current = true;
|
|
194
225
|
const needsFirstSession = chats.length === 0;
|
|
195
226
|
if (!isEmpty || needsFirstSession) {
|
|
196
|
-
const sessionId =
|
|
227
|
+
const sessionId = startEmptyNewChat();
|
|
197
228
|
if (sessionId == null) {
|
|
198
229
|
logger.warn('Chat prompt link: sign in to use the assistant.');
|
|
199
230
|
mutateSearchParams(n => {
|
|
@@ -216,14 +247,14 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
|
|
|
216
247
|
event: 'keydown',
|
|
217
248
|
callback: (e) => {
|
|
218
249
|
const ke = e;
|
|
219
|
-
if (ke.key
|
|
250
|
+
if (ke.key !== CHAT_NEW_SHORTCUT_KEY ||
|
|
220
251
|
!(ke.metaKey || ke.ctrlKey) ||
|
|
221
|
-
|
|
252
|
+
ke.shiftKey ||
|
|
222
253
|
ke.altKey) {
|
|
223
254
|
return;
|
|
224
255
|
}
|
|
225
256
|
ke.preventDefault();
|
|
226
|
-
|
|
257
|
+
startEmptyNewChat();
|
|
227
258
|
onOpenChange(true);
|
|
228
259
|
},
|
|
229
260
|
});
|
|
@@ -875,6 +906,7 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
|
|
|
875
906
|
effectiveScopeId,
|
|
876
907
|
onPromptSubmit: onPromptSubmitWithSlashCommands,
|
|
877
908
|
onChatDeleted: endLocalDemoFlow,
|
|
909
|
+
onNewChat: startNewChatFromSelector,
|
|
878
910
|
promptPrefill: promptLinkPrefill,
|
|
879
911
|
emptyState: resolvedEmptyState,
|
|
880
912
|
allowedAttachments,
|
|
@@ -895,7 +927,7 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
|
|
|
895
927
|
isOpen,
|
|
896
928
|
onOpenChange,
|
|
897
929
|
toggleOpen,
|
|
898
|
-
newChat,
|
|
930
|
+
newChat: startEmptyNewChat,
|
|
899
931
|
openNewChatWithPrefill,
|
|
900
932
|
chatPanelContainer,
|
|
901
933
|
};
|
|
@@ -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] ?? [];
|
|
@@ -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),
|
|
@@ -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;
|
|
@@ -113,4 +113,6 @@ export interface ChatProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
113
113
|
/** Chat context scope; when set, header shows chat selector + delete. */
|
|
114
114
|
scopeId?: string;
|
|
115
115
|
onChatDeleted?: (sessionId: string) => void;
|
|
116
|
+
/** "+ New Chat" in the selector; when omitted, starts an empty session. */
|
|
117
|
+
onNewChat?: () => void;
|
|
116
118
|
}
|
|
@@ -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, 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;
|
|
@@ -34,6 +34,8 @@ export interface ChatChromeProps {
|
|
|
34
34
|
effectiveScopeId: string;
|
|
35
35
|
onPromptSubmit: (message: string, attachments?: ChatAttachmentDropItem[]) => void | Promise<void>;
|
|
36
36
|
onChatDeleted: (sessionId: string) => void;
|
|
37
|
+
/** "+ New Chat" in the header selector; omit for default empty session. */
|
|
38
|
+
onNewChat?: () => void;
|
|
37
39
|
/** `?prompt=` deep link text for one-shot composer pre-fill. */
|
|
38
40
|
promptPrefill?: string | null;
|
|
39
41
|
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;
|
|
@@ -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
|
@@ -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}
|
|
@@ -127,4 +127,6 @@ export interface ChatProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
127
127
|
/** Chat context scope; when set, header shows chat selector + delete. */
|
|
128
128
|
scopeId?: string;
|
|
129
129
|
onChatDeleted?: (sessionId: string) => void;
|
|
130
|
+
/** "+ New Chat" in the selector; when omitted, starts an empty session. */
|
|
131
|
+
onNewChat?: () => void;
|
|
130
132
|
}
|
|
@@ -45,6 +45,7 @@ export function ChatChrome({
|
|
|
45
45
|
effectiveScopeId,
|
|
46
46
|
onPromptSubmit,
|
|
47
47
|
onChatDeleted,
|
|
48
|
+
onNewChat,
|
|
48
49
|
promptPrefill,
|
|
49
50
|
footerClassName,
|
|
50
51
|
emptyState,
|
|
@@ -174,6 +175,7 @@ export function ChatChrome({
|
|
|
174
175
|
isEmpty={isEmpty}
|
|
175
176
|
scopeId={effectiveScopeId}
|
|
176
177
|
onChatDeleted={onChatDeleted}
|
|
178
|
+
onNewChat={onNewChat}
|
|
177
179
|
>
|
|
178
180
|
{isEmpty ? (
|
|
179
181
|
<>
|
|
@@ -47,6 +47,8 @@ export interface ChatChromeProps {
|
|
|
47
47
|
attachments?: ChatAttachmentDropItem[],
|
|
48
48
|
) => void | Promise<void>;
|
|
49
49
|
onChatDeleted: (sessionId: string) => void;
|
|
50
|
+
/** "+ New Chat" in the header selector; omit for default empty session. */
|
|
51
|
+
onNewChat?: () => void;
|
|
50
52
|
/** `?prompt=` deep link text for one-shot composer pre-fill. */
|
|
51
53
|
promptPrefill?: string | null;
|
|
52
54
|
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;
|
|
@@ -86,6 +86,11 @@ export type UseChatPanelChromeModelInput = {
|
|
|
86
86
|
slashCommandItems?: SlashCommandItem[];
|
|
87
87
|
/** Custom slash command handler (palette pick or Enter on `/id`). */
|
|
88
88
|
onSlashItemCommand?: SlashOnItemCommand;
|
|
89
|
+
/**
|
|
90
|
+
* When true, "+ New Chat" in the selector seeds from the current session.
|
|
91
|
+
* Keyboard shortcut and other new-chat entry points always start empty.
|
|
92
|
+
*/
|
|
93
|
+
copyHistoryOnNewChat?: boolean;
|
|
89
94
|
/** Override or extend the default send payload (e.g. api vs display text split). */
|
|
90
95
|
transformSendPayload?: (
|
|
91
96
|
message: string,
|
|
@@ -125,7 +130,7 @@ type PresetScriptState = {
|
|
|
125
130
|
};
|
|
126
131
|
|
|
127
132
|
const SCRIPT_STEP_DELAY_MS = 1200;
|
|
128
|
-
const CHAT_NEW_SHORTCUT_KEY = '
|
|
133
|
+
const CHAT_NEW_SHORTCUT_KEY = '0';
|
|
129
134
|
|
|
130
135
|
const CHAT_QUERY_PARAM = 'chat';
|
|
131
136
|
const CHAT_OPEN_VALUE = 'open';
|
|
@@ -144,6 +149,7 @@ export function useChatPanelChromeModel({
|
|
|
144
149
|
onAttachmentsDropped,
|
|
145
150
|
slashCommandItems,
|
|
146
151
|
onSlashItemCommand,
|
|
152
|
+
copyHistoryOnNewChat = false,
|
|
147
153
|
transformSendPayload,
|
|
148
154
|
}: UseChatPanelChromeModelInput): UseChatPanelChromeModelResult {
|
|
149
155
|
const effectiveScopeId = scopeId ?? NO_SCOPE_FALLBACK;
|
|
@@ -337,9 +343,44 @@ export function useChatPanelChromeModel({
|
|
|
337
343
|
|
|
338
344
|
const isEmpty = isChatEmpty(chat) && !isLoading;
|
|
339
345
|
|
|
346
|
+
const startEmptyNewChat = useCallback(() => {
|
|
347
|
+
const priorChatId = currentChatId;
|
|
348
|
+
const sessionId = newChat();
|
|
349
|
+
if (sessionId && priorChatId) {
|
|
350
|
+
endLocalDemoFlow(priorChatId);
|
|
351
|
+
}
|
|
352
|
+
if (sessionId) {
|
|
353
|
+
setPromptLinkPrefill(null);
|
|
354
|
+
}
|
|
355
|
+
return sessionId;
|
|
356
|
+
}, [newChat, currentChatId, endLocalDemoFlow]);
|
|
357
|
+
|
|
358
|
+
const startNewChatFromSelector = useCallback(() => {
|
|
359
|
+
const priorChatId = currentChatId;
|
|
360
|
+
const messages = chat?.messages ?? [];
|
|
361
|
+
const sessionId = newChat(
|
|
362
|
+
copyHistoryOnNewChat && messages.length > 0
|
|
363
|
+
? { seedMessages: messages }
|
|
364
|
+
: undefined,
|
|
365
|
+
);
|
|
366
|
+
if (sessionId && priorChatId) {
|
|
367
|
+
endLocalDemoFlow(priorChatId);
|
|
368
|
+
}
|
|
369
|
+
if (sessionId) {
|
|
370
|
+
setPromptLinkPrefill(null);
|
|
371
|
+
}
|
|
372
|
+
return sessionId;
|
|
373
|
+
}, [
|
|
374
|
+
newChat,
|
|
375
|
+
copyHistoryOnNewChat,
|
|
376
|
+
chat?.messages,
|
|
377
|
+
currentChatId,
|
|
378
|
+
endLocalDemoFlow,
|
|
379
|
+
]);
|
|
380
|
+
|
|
340
381
|
const openNewChatWithPrefill = useCallback(
|
|
341
382
|
(prompt: string) => {
|
|
342
|
-
const sessionId =
|
|
383
|
+
const sessionId = startEmptyNewChat();
|
|
343
384
|
if (sessionId == null) {
|
|
344
385
|
logger.warn('Chat prefill: sign in to use the assistant.');
|
|
345
386
|
return;
|
|
@@ -349,7 +390,7 @@ export function useChatPanelChromeModel({
|
|
|
349
390
|
setPromptLinkPrefill(trimmed || null);
|
|
350
391
|
onOpenChange(true);
|
|
351
392
|
},
|
|
352
|
-
[
|
|
393
|
+
[startEmptyNewChat, onOpenChange],
|
|
353
394
|
);
|
|
354
395
|
|
|
355
396
|
/**
|
|
@@ -381,7 +422,7 @@ export function useChatPanelChromeModel({
|
|
|
381
422
|
const needsFirstSession = chats.length === 0;
|
|
382
423
|
|
|
383
424
|
if (!isEmpty || needsFirstSession) {
|
|
384
|
-
const sessionId =
|
|
425
|
+
const sessionId = startEmptyNewChat();
|
|
385
426
|
if (sessionId == null) {
|
|
386
427
|
logger.warn('Chat prompt link: sign in to use the assistant.');
|
|
387
428
|
mutateSearchParams(n => {
|
|
@@ -408,15 +449,15 @@ export function useChatPanelChromeModel({
|
|
|
408
449
|
callback: (e: Event) => {
|
|
409
450
|
const ke = e as KeyboardEvent;
|
|
410
451
|
if (
|
|
411
|
-
ke.key
|
|
452
|
+
ke.key !== CHAT_NEW_SHORTCUT_KEY ||
|
|
412
453
|
!(ke.metaKey || ke.ctrlKey) ||
|
|
413
|
-
|
|
454
|
+
ke.shiftKey ||
|
|
414
455
|
ke.altKey
|
|
415
456
|
) {
|
|
416
457
|
return;
|
|
417
458
|
}
|
|
418
459
|
ke.preventDefault();
|
|
419
|
-
|
|
460
|
+
startEmptyNewChat();
|
|
420
461
|
onOpenChange(true);
|
|
421
462
|
},
|
|
422
463
|
});
|
|
@@ -1192,6 +1233,7 @@ export function useChatPanelChromeModel({
|
|
|
1192
1233
|
effectiveScopeId,
|
|
1193
1234
|
onPromptSubmit: onPromptSubmitWithSlashCommands,
|
|
1194
1235
|
onChatDeleted: endLocalDemoFlow,
|
|
1236
|
+
onNewChat: startNewChatFromSelector,
|
|
1195
1237
|
promptPrefill: promptLinkPrefill,
|
|
1196
1238
|
emptyState: resolvedEmptyState,
|
|
1197
1239
|
allowedAttachments,
|
|
@@ -1214,7 +1256,7 @@ export function useChatPanelChromeModel({
|
|
|
1214
1256
|
isOpen,
|
|
1215
1257
|
onOpenChange,
|
|
1216
1258
|
toggleOpen,
|
|
1217
|
-
newChat,
|
|
1259
|
+
newChat: startEmptyNewChat,
|
|
1218
1260
|
openNewChatWithPrefill,
|
|
1219
1261
|
chatPanelContainer,
|
|
1220
1262
|
};
|
|
@@ -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 => {
|
|
@@ -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,
|