@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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sybilion/uilib",
3
- "version": "1.3.39",
3
+ "version": "1.3.40",
4
4
  "description": "Sybilion Design System — React UI components (Webpack + Stylus)",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -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}>{text}</div>
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 ASSISTANT_REPLY_TEXT =
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(role: MessageRole, text: string): Message {
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
- makeMessage(MessageRole.ASSISTANT, SAMPLE_COMMAND_REPLY_TEXT),
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, or send{' '}
131
- <kbd className="font-mono">/sample-command</kbd> with Enter.
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 and runs the demo action.',
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>