@sybilion/uilib 1.3.39 → 1.3.40
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/ChatChrome/ChatChrome.js +1 -1
- package/dist/esm/components/ui/Chat/ChatMessage/ChatMessage.js +3 -2
- package/dist/esm/contexts/chat-context.js +1 -0
- package/dist/esm/types/src/components/ui/Chat/Chat.types.d.ts +3 -0
- package/dist/esm/types/src/components/ui/Chat/ChatMessage/ChatMessage.d.ts +1 -1
- package/dist/esm/types/src/contexts/chat-context.d.ts +2 -0
- package/package.json +1 -1
- package/src/components/ui/Chat/Chat.types.ts +3 -0
- package/src/components/ui/Chat/ChatChrome/ChatChrome.tsx +1 -0
- package/src/components/ui/Chat/ChatMessage/ChatMessage.tsx +5 -1
- package/src/contexts/chat-context.tsx +3 -0
- package/src/docs/pages/ChatSlashCommandsPage.tsx +37 -8
|
@@ -68,7 +68,7 @@ function ChatChrome({ showResizeHandle, resizeHandle, onClose, isEmpty, renderPr
|
|
|
68
68
|
}, [isEmpty, messages.length]);
|
|
69
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: promptBusy, 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) => {
|
|
70
70
|
const isLast = index === arr.length - 1;
|
|
71
|
-
return (jsx(Chat.Message, { role: msg.role, text: msg.text, userTextFileAttachments: msg.userTextFileAttachments, onQuickReply: onQuickReply, suppressedQuickReplyKeys: suppressedQuickReplyKeys, quickReplyDisabled: isLoading, isLastMessage: isLast, scriptContinue: isLast && scriptContinueLabel
|
|
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 }
|
|
73
73
|
: undefined, onScriptContinue: isLast && scriptContinueLabel
|
|
74
74
|
? onScriptContinue
|
|
@@ -3,19 +3,20 @@ import cn from 'classnames';
|
|
|
3
3
|
import { InteractiveContent } from '../../InteractiveContent/InteractiveContent.js';
|
|
4
4
|
import 'lucide-react';
|
|
5
5
|
import '../../InteractiveContent/InteractiveContent.styl.js';
|
|
6
|
+
import { TextShimmer } from '../../TextShimmer/TextShimmer.js';
|
|
6
7
|
import { MessageRole } from '../Chat.types.js';
|
|
7
8
|
import { userTextFileAttachmentsFromMessage } from '../userTextFileAttachments.js';
|
|
8
9
|
import { AgentMessageContent } from './AgentMessageContent.js';
|
|
9
10
|
import S from './ChatMessage.styl.js';
|
|
10
11
|
import { UserTextFileAttachmentBubble } from './UserTextFileAttachmentBubble.js';
|
|
11
12
|
|
|
12
|
-
function ChatMessage({ role, text, userTextFileAttachments, onQuickReply, suppressedQuickReplyKeys, quickReplyDisabled, isLastMessage = true, scriptContinue, onScriptContinue, renderMessageChart, }) {
|
|
13
|
+
function ChatMessage({ role, text, inProgress, userTextFileAttachments, onQuickReply, suppressedQuickReplyKeys, quickReplyDisabled, isLastMessage = true, scriptContinue, onScriptContinue, renderMessageChart, }) {
|
|
13
14
|
const fileAttachments = userTextFileAttachmentsFromMessage({
|
|
14
15
|
userTextFileAttachments,
|
|
15
16
|
});
|
|
16
17
|
const isAssistant = role === MessageRole.ASSISTANT;
|
|
17
18
|
const isSystem = role === MessageRole.SYSTEM;
|
|
18
|
-
return (jsx("div", { className: cn(S.root, S[`role-${role}`]), children: isSystem ? (jsx("div", { className: S.text, children: text })) : isAssistant ? (jsx(AgentMessageContent, { text: text, onQuickReply: onQuickReply, suppressedQuickReplyKeys: suppressedQuickReplyKeys, quickReplyDisabled: quickReplyDisabled, isLastMessage: isLastMessage, scriptContinue: scriptContinue, onScriptContinue: onScriptContinue, renderMessageChart: renderMessageChart })) : (jsxs("div", { className: S.userColumn, children: [jsx("div", { className: S.text, children: jsx(InteractiveContent, { text: text }) }), fileAttachments.map(attachment => (jsx(UserTextFileAttachmentBubble, { attachment: attachment }, `${attachment.displayName}:${attachment.filename}`)))] })) }));
|
|
19
|
+
return (jsx("div", { className: cn(S.root, S[`role-${role}`]), children: isSystem ? (jsx("div", { className: S.text, children: inProgress ? jsx(TextShimmer, { as: "span", children: text }) : text })) : isAssistant ? (jsx(AgentMessageContent, { text: text, onQuickReply: onQuickReply, suppressedQuickReplyKeys: suppressedQuickReplyKeys, quickReplyDisabled: quickReplyDisabled, isLastMessage: isLastMessage, scriptContinue: scriptContinue, onScriptContinue: onScriptContinue, renderMessageChart: renderMessageChart })) : (jsxs("div", { className: S.userColumn, children: [jsx("div", { className: S.text, children: jsx(InteractiveContent, { text: text }) }), fileAttachments.map(attachment => (jsx(UserTextFileAttachmentBubble, { attachment: attachment }, `${attachment.displayName}:${attachment.filename}`)))] })) }));
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export { ChatMessage };
|
|
@@ -200,6 +200,7 @@ function ChatProvider({ children, userSwitchKey, sendChatMessage: sendChatMessag
|
|
|
200
200
|
...(attachments?.length
|
|
201
201
|
? { userTextFileAttachments: attachments }
|
|
202
202
|
: {}),
|
|
203
|
+
...(options?.inProgress ? { inProgress: true } : {}),
|
|
203
204
|
};
|
|
204
205
|
setChats(prev => {
|
|
205
206
|
const scopeChats = prev[scopeId] ?? [];
|
|
@@ -24,6 +24,8 @@ export interface Message {
|
|
|
24
24
|
text: string;
|
|
25
25
|
timestamp: number;
|
|
26
26
|
userTextFileAttachments?: UserTextFileAttachment[];
|
|
27
|
+
/** SYSTEM-only: transient progress placeholder while work is in flight. */
|
|
28
|
+
inProgress?: boolean;
|
|
27
29
|
}
|
|
28
30
|
export interface Chat {
|
|
29
31
|
session_id: string;
|
|
@@ -88,6 +90,7 @@ export interface ChatPromptProps {
|
|
|
88
90
|
export interface ChatMessageProps {
|
|
89
91
|
role: MessageRole;
|
|
90
92
|
text: string;
|
|
93
|
+
inProgress?: boolean;
|
|
91
94
|
userTextFileAttachments?: UserTextFileAttachment[];
|
|
92
95
|
onQuickReply?: (branchKey: string, displayLabel: string) => void;
|
|
93
96
|
/** Branch keys already taken (e.g. from chat history); hide quick-reply buttons for these. */
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { type ChatMessageProps } from '../Chat.types';
|
|
2
|
-
export declare function ChatMessage({ role, text, userTextFileAttachments, onQuickReply, suppressedQuickReplyKeys, quickReplyDisabled, isLastMessage, scriptContinue, onScriptContinue, renderMessageChart, }: ChatMessageProps): import("react/jsx-runtime").JSX.Element;
|
|
2
|
+
export declare function ChatMessage({ role, text, inProgress, userTextFileAttachments, onQuickReply, suppressedQuickReplyKeys, quickReplyDisabled, isLastMessage, scriptContinue, onScriptContinue, renderMessageChart, }: ChatMessageProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -5,6 +5,8 @@ export type SendChatMessageFn = (message: string, targetChatId: string) => Promi
|
|
|
5
5
|
export type { ChatSendMessagePayload, UserTextFileAttachment, } from '#uilib/components/ui/Chat/Chat.types';
|
|
6
6
|
export type AddChatMessageOptions = {
|
|
7
7
|
userTextFileAttachments?: UserTextFileAttachment[];
|
|
8
|
+
/** SYSTEM-only: mark as transient progress placeholder (shimmer until removed). */
|
|
9
|
+
inProgress?: boolean;
|
|
8
10
|
};
|
|
9
11
|
export interface ChatContextType {
|
|
10
12
|
/** Returns the new session id, or undefined if no user / not created. */
|
package/package.json
CHANGED
|
@@ -33,6 +33,8 @@ export interface Message {
|
|
|
33
33
|
text: string;
|
|
34
34
|
timestamp: number;
|
|
35
35
|
userTextFileAttachments?: UserTextFileAttachment[];
|
|
36
|
+
/** SYSTEM-only: transient progress placeholder while work is in flight. */
|
|
37
|
+
inProgress?: boolean;
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
export interface Chat {
|
|
@@ -103,6 +105,7 @@ export interface ChatPromptProps {
|
|
|
103
105
|
export interface ChatMessageProps {
|
|
104
106
|
role: MessageRole;
|
|
105
107
|
text: string;
|
|
108
|
+
inProgress?: boolean;
|
|
106
109
|
userTextFileAttachments?: UserTextFileAttachment[];
|
|
107
110
|
onQuickReply?: (branchKey: string, displayLabel: string) => void;
|
|
108
111
|
/** Branch keys already taken (e.g. from chat history); hide quick-reply buttons for these. */
|
|
@@ -199,6 +199,7 @@ export function ChatChrome({
|
|
|
199
199
|
key={msg.id}
|
|
200
200
|
role={msg.role}
|
|
201
201
|
text={msg.text}
|
|
202
|
+
inProgress={msg.inProgress}
|
|
202
203
|
userTextFileAttachments={msg.userTextFileAttachments}
|
|
203
204
|
onQuickReply={onQuickReply}
|
|
204
205
|
suppressedQuickReplyKeys={suppressedQuickReplyKeys}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import cn from 'classnames';
|
|
2
2
|
|
|
3
3
|
import { InteractiveContent } from '#uilib/components/ui/InteractiveContent';
|
|
4
|
+
import { TextShimmer } from '#uilib/components/ui/TextShimmer';
|
|
4
5
|
|
|
5
6
|
import { type ChatMessageProps, MessageRole } from '../Chat.types';
|
|
6
7
|
import { userTextFileAttachmentsFromMessage } from '../userTextFileAttachments';
|
|
@@ -11,6 +12,7 @@ import { UserTextFileAttachmentBubble } from './UserTextFileAttachmentBubble';
|
|
|
11
12
|
export function ChatMessage({
|
|
12
13
|
role,
|
|
13
14
|
text,
|
|
15
|
+
inProgress,
|
|
14
16
|
userTextFileAttachments,
|
|
15
17
|
onQuickReply,
|
|
16
18
|
suppressedQuickReplyKeys,
|
|
@@ -29,7 +31,9 @@ export function ChatMessage({
|
|
|
29
31
|
return (
|
|
30
32
|
<div className={cn(S.root, S[`role-${role}`])}>
|
|
31
33
|
{isSystem ? (
|
|
32
|
-
<div className={S.text}>
|
|
34
|
+
<div className={S.text}>
|
|
35
|
+
{inProgress ? <TextShimmer as="span">{text}</TextShimmer> : text}
|
|
36
|
+
</div>
|
|
33
37
|
) : isAssistant ? (
|
|
34
38
|
<AgentMessageContent
|
|
35
39
|
text={text}
|
|
@@ -35,6 +35,8 @@ const CHAT_SCOPE_IDS_REGISTRY_KEY = 'chat-scope-ids';
|
|
|
35
35
|
|
|
36
36
|
export type AddChatMessageOptions = {
|
|
37
37
|
userTextFileAttachments?: UserTextFileAttachment[];
|
|
38
|
+
/** SYSTEM-only: mark as transient progress placeholder (shimmer until removed). */
|
|
39
|
+
inProgress?: boolean;
|
|
38
40
|
};
|
|
39
41
|
|
|
40
42
|
export interface ChatContextType {
|
|
@@ -326,6 +328,7 @@ export function ChatProvider({
|
|
|
326
328
|
...(attachments?.length
|
|
327
329
|
? { userTextFileAttachments: attachments }
|
|
328
330
|
: {}),
|
|
331
|
+
...(options?.inProgress ? { inProgress: true } : {}),
|
|
329
332
|
};
|
|
330
333
|
|
|
331
334
|
setChats(prev => {
|
|
@@ -31,18 +31,25 @@ const DOCS_SAMPLE_SLASH_ITEMS: SlashCommandItem[] = [
|
|
|
31
31
|
const SAMPLE_COMMAND_REPLY_TEXT =
|
|
32
32
|
'Sample command ran via `onSlashItemCommand` — composer cleared, no mention inserted.';
|
|
33
33
|
|
|
34
|
-
const
|
|
35
|
-
'Demo reply for a normal message. Slash picks with a custom handler skip mention insert.';
|
|
34
|
+
const SAMPLE_COMMAND_PROGRESS_TEXT = 'Running sample command…';
|
|
36
35
|
|
|
37
|
-
function makeMessage(
|
|
36
|
+
function makeMessage(
|
|
37
|
+
role: MessageRole,
|
|
38
|
+
text: string,
|
|
39
|
+
options?: { inProgress?: boolean },
|
|
40
|
+
): Message {
|
|
38
41
|
return {
|
|
39
42
|
id: crypto.randomUUID(),
|
|
40
43
|
role,
|
|
41
44
|
text,
|
|
42
45
|
timestamp: Date.now(),
|
|
46
|
+
...(options?.inProgress ? { inProgress: true } : {}),
|
|
43
47
|
};
|
|
44
48
|
}
|
|
45
49
|
|
|
50
|
+
const ASSISTANT_REPLY_TEXT =
|
|
51
|
+
'Demo reply for a normal message. Slash picks with a custom handler skip mention insert.';
|
|
52
|
+
|
|
46
53
|
export default function ChatSlashCommandsPage() {
|
|
47
54
|
const [messages, setMessages] = useState<Message[]>([]);
|
|
48
55
|
const [isLoading, setIsLoading] = useState(false);
|
|
@@ -63,10 +70,30 @@ export default function ChatSlashCommandsPage() {
|
|
|
63
70
|
messages[messages.length - 1]?.role === MessageRole.USER;
|
|
64
71
|
|
|
65
72
|
const runSampleCommand = useCallback(() => {
|
|
73
|
+
const progressId = crypto.randomUUID();
|
|
74
|
+
setIsLoading(true);
|
|
66
75
|
setMessages(prev => [
|
|
67
76
|
...prev,
|
|
68
|
-
|
|
77
|
+
{
|
|
78
|
+
id: progressId,
|
|
79
|
+
role: MessageRole.SYSTEM,
|
|
80
|
+
text: SAMPLE_COMMAND_PROGRESS_TEXT,
|
|
81
|
+
timestamp: Date.now(),
|
|
82
|
+
inProgress: true,
|
|
83
|
+
},
|
|
69
84
|
]);
|
|
85
|
+
|
|
86
|
+
if (replyTimeoutRef.current != null) {
|
|
87
|
+
clearTimeout(replyTimeoutRef.current);
|
|
88
|
+
}
|
|
89
|
+
replyTimeoutRef.current = setTimeout(() => {
|
|
90
|
+
replyTimeoutRef.current = null;
|
|
91
|
+
setMessages(prev => [
|
|
92
|
+
...prev.filter(m => m.id !== progressId),
|
|
93
|
+
makeMessage(MessageRole.ASSISTANT, SAMPLE_COMMAND_REPLY_TEXT),
|
|
94
|
+
]);
|
|
95
|
+
setIsLoading(false);
|
|
96
|
+
}, 1200);
|
|
70
97
|
}, []);
|
|
71
98
|
|
|
72
99
|
const onSlashItemCommand = useCallback(
|
|
@@ -113,7 +140,7 @@ export default function ChatSlashCommandsPage() {
|
|
|
113
140
|
<AppPageHeader
|
|
114
141
|
breadcrumbs={[{ label: 'Chat' }, { label: 'Chat slash commands' }]}
|
|
115
142
|
title="Chat slash commands"
|
|
116
|
-
subheader={`Slash palette uses TipTap Mention with "/" as the trigger. Pass slashCommandItems from the app; optional onSlashItemCommand can clear the composer and run an action instead of inserting a mention.`}
|
|
143
|
+
subheader={`Slash palette uses TipTap Mention with "/" as the trigger. Pass slashCommandItems from the app; optional onSlashItemCommand can clear the composer and run an action instead of inserting a mention. Long-running handlers can append a SYSTEM message with inProgress while work is in flight, then remove it when done.`}
|
|
117
144
|
actions={
|
|
118
145
|
<DocsHeaderActions slashCommandItems={DOCS_SAMPLE_SLASH_ITEMS} />
|
|
119
146
|
}
|
|
@@ -127,8 +154,10 @@ export default function ChatSlashCommandsPage() {
|
|
|
127
154
|
shell below: scrolling history, empty state, disclaimer, composer.
|
|
128
155
|
Type <kbd className="font-mono">/</kbd> at line start or after a
|
|
129
156
|
space; pick <kbd className="font-mono">sample-command</kbd> to run the
|
|
130
|
-
custom handler
|
|
131
|
-
<kbd className="font-mono"
|
|
157
|
+
custom handler (shows a transient{' '}
|
|
158
|
+
<kbd className="font-mono">inProgress</kbd> shimmer while the demo
|
|
159
|
+
action runs), or send <kbd className="font-mono">/sample-command</kbd>{' '}
|
|
160
|
+
with Enter.
|
|
132
161
|
</p>
|
|
133
162
|
<ChatChrome
|
|
134
163
|
showResizeHandle={false}
|
|
@@ -156,7 +185,7 @@ export default function ChatSlashCommandsPage() {
|
|
|
156
185
|
emptyState={{
|
|
157
186
|
title: 'Try a slash command',
|
|
158
187
|
description:
|
|
159
|
-
'Pick sample-command from the palette or send /sample-command — onSlashItemCommand clears the composer
|
|
188
|
+
'Pick sample-command from the palette or send /sample-command — onSlashItemCommand clears the composer, shows an inProgress placeholder, then posts the demo reply.',
|
|
160
189
|
}}
|
|
161
190
|
/>
|
|
162
191
|
</PageContentSection>
|