@sybilion/uilib 1.3.79 → 1.3.80

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.
@@ -10,6 +10,7 @@ import { DropZone } from '../../DropZone/DropZone.js';
10
10
  import { PanelResizeHandle } from '../../Sidebar/Sidebar.js';
11
11
  import SidebarStem from '../../Sidebar/Sidebar.styl.js';
12
12
  import { Chat } from '../Chat.js';
13
+ import { MessageRole } from '../Chat.types.js';
13
14
  import { filterToTextAttachments, isAttachmentsDropzoneEnabled, buildAcceptAttr } from '../chatAttachmentAccept.js';
14
15
  import { extractChatAttachmentItems } from '../chatAttachmentExtract.js';
15
16
  import S from './ChatChrome.styl.js';
@@ -21,6 +22,7 @@ function ChatChrome({ showResizeHandle, resizeHandle, onClose, isEmpty, renderPr
21
22
  const [pendingAttachments, setPendingAttachments] = useState([]);
22
23
  const [isExtractingAttachments, setIsExtractingAttachments] = useState(false);
23
24
  const promptDisabled = isExtractingAttachments;
25
+ const hasInProgressSystemMessage = useMemo(() => messages.some(msg => msg.role === MessageRole.SYSTEM && msg.inProgress === true), [messages]);
24
26
  const handleAttachmentFiles = useCallback((files) => {
25
27
  if (promptDisabled || files.length === 0)
26
28
  return;
@@ -85,7 +87,9 @@ function ChatChrome({ showResizeHandle, resizeHandle, onClose, isEmpty, renderPr
85
87
  const label = displayLabelForBranchKeyFromMessages(key, messages) ??
86
88
  humanizeBranchKey(key);
87
89
  return (jsx("span", { className: S.branchBtnWrap, children: jsxs(Button, { type: "button", variant: "outline", size: "sm", disabled: isLoading, onClick: () => onQuickReply(key, label), children: [jsx(PaperPlaneRightIcon, {}), label] }) }, key));
88
- }) })) : null, showInlinePresets && renderPresets('inline'), isLoading && (isLastMessageFromUser || loadingLabel) && (jsx(TextShimmer, { duration: 1, spread: 5, className: S.loader, children: loadingLabel ?? 'Thinking...' }))] }) })), isEmpty ? (jsx("div", { className: S.fixedPresets, children: renderPresets('fixed') })) : null, jsxs("div", { className: cn(S.footer, footerClassName), children: [isEmpty ? (jsx("div", { className: S.notice, children: "Forecast Assistant can make mistakes." })) : null, jsx(Chat.Prompt, { onSubmit: handlePromptSubmitWithAttachments, disabled: promptDisabled, attachments: pendingAttachments, onRemoveAttachment: handleRemoveAttachment, prefillMessage: promptPrefill ?? undefined, placeholder: promptPlaceholder, slashCommandItems: slashCommandItems, onSlashItemCommand: onSlashItemCommand, attachmentAccept: attachmentsDropzoneEnabled ? attachmentAccept : undefined, onAttachmentFiles: attachmentsDropzoneEnabled ? handleAttachmentFiles : undefined })] })] })] })] }));
90
+ }) })) : null, showInlinePresets && renderPresets('inline'), isLoading &&
91
+ !hasInProgressSystemMessage &&
92
+ (isLastMessageFromUser || loadingLabel) && (jsx(TextShimmer, { duration: 1, spread: 5, className: S.loader, children: loadingLabel ?? 'Thinking...' }))] }) })), isEmpty ? (jsx("div", { className: S.fixedPresets, children: renderPresets('fixed') })) : null, jsxs("div", { className: cn(S.footer, footerClassName), children: [isEmpty ? (jsx("div", { className: S.notice, children: "Forecast Assistant can make mistakes." })) : null, jsx(Chat.Prompt, { onSubmit: handlePromptSubmitWithAttachments, disabled: promptDisabled, attachments: pendingAttachments, onRemoveAttachment: handleRemoveAttachment, prefillMessage: promptPrefill ?? undefined, placeholder: promptPlaceholder, slashCommandItems: slashCommandItems, onSlashItemCommand: onSlashItemCommand, attachmentAccept: attachmentsDropzoneEnabled ? attachmentAccept : undefined, onAttachmentFiles: attachmentsDropzoneEnabled ? handleAttachmentFiles : undefined })] })] })] })] }));
89
93
  }
90
94
 
91
95
  export { ChatChrome };
@@ -68,7 +68,9 @@ function displayTextFromSendPayload(message) {
68
68
  }
69
69
  /** Optional loading shimmer label from a structured send payload. */
70
70
  function loadingLabelFromSendPayload(message) {
71
- return typeof message === 'string' ? undefined : message.loadingLabel;
71
+ if (typeof message === 'string')
72
+ return undefined;
73
+ return message.loadingLabel ?? message.systemProgressLabel;
72
74
  }
73
75
 
74
76
  export { buildChatSendMessagePayload, displayTextFromSendPayload, loadingLabelFromSendPayload, normalizeUserTextFileAttachments };
@@ -97,12 +97,14 @@ function shiftNormalizedSeriesBackward(series, lagMonths) {
97
97
  function applyDriversComparisonViewToPayload(payload, tab) {
98
98
  if (!payload || tab === 'lagged')
99
99
  return payload;
100
+ if (!payload.target?.normalized_series)
101
+ return payload;
100
102
  return {
101
103
  target: {
102
104
  ...payload.target,
103
105
  normalized_series: { ...payload.target.normalized_series },
104
106
  },
105
- drivers: payload.drivers.map(driver => {
107
+ drivers: (payload.drivers ?? []).map(driver => {
106
108
  const lagMonths = parseLagMonthsFromLabel(resolveDriverLagLabel(driver));
107
109
  const series = driver.normalized_series ?? {};
108
110
  const normalized_series = lagMonths != null && lagMonths > 0
@@ -343,13 +343,19 @@ function ChatProvider({ children, userSwitchKey, sendChatMessage: sendChatMessag
343
343
  userTextFileAttachments: normalizeUserTextFileAttachments(message),
344
344
  });
345
345
  }
346
+ let progressMessageId;
347
+ if (typeof message !== 'string' && message.systemProgressLabel) {
348
+ progressMessageId = addMessage(scopeId, targetChatId, MessageRole.SYSTEM, message.systemProgressLabel, { inProgress: true });
349
+ }
346
350
  const pendingChatSessionId = targetChatId;
347
351
  beginOutboundPending(scopeId, pendingChatSessionId);
352
+ let effectiveSessionId = pendingChatSessionId;
348
353
  try {
349
354
  const data = await sendChatMessageFn(apiPayload, pendingChatSessionId);
350
- const effectiveSessionId = data.session_id && data.session_id !== pendingChatSessionId
351
- ? data.session_id
352
- : pendingChatSessionId;
355
+ effectiveSessionId =
356
+ data.session_id && data.session_id !== pendingChatSessionId
357
+ ? data.session_id
358
+ : pendingChatSessionId;
353
359
  if (data.session_id && data.session_id !== pendingChatSessionId) {
354
360
  setChats(prev => {
355
361
  const scopeChats = prev[scopeId] ?? [];
@@ -362,14 +368,21 @@ function ChatProvider({ children, userSwitchKey, sendChatMessage: sendChatMessag
362
368
  });
363
369
  setCurrentChatId(scopeId, data.session_id);
364
370
  }
371
+ if (progressMessageId) {
372
+ removeMessageById(scopeId, effectiveSessionId, progressMessageId);
373
+ progressMessageId = undefined;
374
+ }
365
375
  addMessage(scopeId, effectiveSessionId, MessageRole.ASSISTANT, data.response);
366
376
  return { response: data.response, sessionId: effectiveSessionId };
367
377
  }
368
378
  catch (error) {
379
+ if (progressMessageId) {
380
+ removeMessageById(scopeId, effectiveSessionId, progressMessageId);
381
+ }
369
382
  const errorMessage = error instanceof Error
370
383
  ? error.message
371
384
  : 'Sorry, I encountered an error processing your message. Please try again.';
372
- addMessage(scopeId, pendingChatSessionId, MessageRole.ASSISTANT, errorMessage);
385
+ addMessage(scopeId, effectiveSessionId, MessageRole.ASSISTANT, errorMessage);
373
386
  throw error;
374
387
  }
375
388
  finally {
@@ -377,6 +390,7 @@ function ChatProvider({ children, userSwitchKey, sendChatMessage: sendChatMessag
377
390
  }
378
391
  }, [
379
392
  addMessage,
393
+ removeMessageById,
380
394
  beginOutboundPending,
381
395
  endOutboundPending,
382
396
  getCurrentChatId,
@@ -21,6 +21,8 @@ export type ChatSendMessagePayload = {
21
21
  omitUserMessage?: boolean;
22
22
  /** Shimmer label while waiting on the API; defaults to "Thinking...". */
23
23
  loadingLabel?: string;
24
+ /** When set, show as a SYSTEM inProgress bubble instead of the footer shimmer. */
25
+ systemProgressLabel?: string;
24
26
  };
25
27
  export type ChatMetaValue = string | number | boolean | null;
26
28
  export type ChatMeta = Record<string, ChatMetaValue>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sybilion/uilib",
3
- "version": "1.3.79",
3
+ "version": "1.3.80",
4
4
  "description": "Sybilion Design System — React UI components (Webpack + Stylus)",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -29,6 +29,8 @@ export type ChatSendMessagePayload = {
29
29
  omitUserMessage?: boolean;
30
30
  /** Shimmer label while waiting on the API; defaults to "Thinking...". */
31
31
  loadingLabel?: string;
32
+ /** When set, show as a SYSTEM inProgress bubble instead of the footer shimmer. */
33
+ systemProgressLabel?: string;
32
34
  };
33
35
 
34
36
  export type ChatMetaValue = string | number | boolean | null;
@@ -14,7 +14,7 @@ import { DropZone } from '../../DropZone/DropZone';
14
14
  import { PanelResizeHandle } from '../../Sidebar/Sidebar';
15
15
  import SidebarStem from '../../Sidebar/Sidebar.styl';
16
16
  import { Chat } from '../Chat';
17
- import type { ChatAttachmentDropItem } from '../Chat.types';
17
+ import { type ChatAttachmentDropItem, MessageRole } from '../Chat.types';
18
18
  import {
19
19
  buildAcceptAttr,
20
20
  filterToTextAttachments,
@@ -76,6 +76,13 @@ export function ChatChrome({
76
76
  >([]);
77
77
  const [isExtractingAttachments, setIsExtractingAttachments] = useState(false);
78
78
  const promptDisabled = isExtractingAttachments;
79
+ const hasInProgressSystemMessage = useMemo(
80
+ () =>
81
+ messages.some(
82
+ msg => msg.role === MessageRole.SYSTEM && msg.inProgress === true,
83
+ ),
84
+ [messages],
85
+ );
79
86
 
80
87
  const handleAttachmentFiles = useCallback(
81
88
  (files: File[]) => {
@@ -263,11 +270,13 @@ export function ChatChrome({
263
270
 
264
271
  {showInlinePresets && renderPresets('inline')}
265
272
 
266
- {isLoading && (isLastMessageFromUser || loadingLabel) && (
267
- <TextShimmer duration={1} spread={5} className={S.loader}>
268
- {loadingLabel ?? 'Thinking...'}
269
- </TextShimmer>
270
- )}
273
+ {isLoading &&
274
+ !hasInProgressSystemMessage &&
275
+ (isLastMessageFromUser || loadingLabel) && (
276
+ <TextShimmer duration={1} spread={5} className={S.loader}>
277
+ {loadingLabel ?? 'Thinking...'}
278
+ </TextShimmer>
279
+ )}
271
280
  </Scroll>
272
281
  </div>
273
282
  )}
@@ -85,5 +85,6 @@ export function displayTextFromSendPayload(
85
85
  export function loadingLabelFromSendPayload(
86
86
  message: string | ChatSendMessagePayload,
87
87
  ): string | undefined {
88
- return typeof message === 'string' ? undefined : message.loadingLabel;
88
+ if (typeof message === 'string') return undefined;
89
+ return message.loadingLabel ?? message.systemProgressLabel;
89
90
  }
@@ -127,7 +127,16 @@ describe('applyDriversComparisonViewToPayload', () => {
127
127
  };
128
128
 
129
129
  it('returns original payload for lagged tab', () => {
130
- expect(applyDriversComparisonViewToPayload(payload, 'lagged')).toBe(payload);
130
+ expect(applyDriversComparisonViewToPayload(payload, 'lagged')).toBe(
131
+ payload,
132
+ );
133
+ });
134
+
135
+ it('returns original payload for overlapped tab when target series missing', () => {
136
+ const incomplete = { drivers: payload.drivers } as typeof payload;
137
+ expect(applyDriversComparisonViewToPayload(incomplete, 'overlapped')).toBe(
138
+ incomplete,
139
+ );
131
140
  });
132
141
 
133
142
  it('shifts driver series backward for overlapped tab without mutating source', () => {
@@ -327,8 +336,8 @@ describe('buildDriversComparisonChartData historical window floor', () => {
327
336
 
328
337
  expect(laggedChart[0]?.date).toBe('2014-10-01');
329
338
  expect(overlappedChart[0]?.date).toBe('2014-07-01');
330
- expect(
331
- overlappedChart[0]?.date!.localeCompare(laggedChart[0]?.date!),
332
- ).toBe(-1);
339
+ expect(overlappedChart[0]?.date!.localeCompare(laggedChart[0]?.date!)).toBe(
340
+ -1,
341
+ );
333
342
  });
334
343
  });
@@ -125,13 +125,14 @@ export function applyDriversComparisonViewToPayload(
125
125
  tab: DriversComparisonViewTab,
126
126
  ): BacktestsComponentPayload | null {
127
127
  if (!payload || tab === 'lagged') return payload;
128
+ if (!payload.target?.normalized_series) return payload;
128
129
 
129
130
  return {
130
131
  target: {
131
132
  ...payload.target,
132
133
  normalized_series: { ...payload.target.normalized_series },
133
134
  },
134
- drivers: payload.drivers.map(driver => {
135
+ drivers: (payload.drivers ?? []).map(driver => {
135
136
  const lagMonths = parseLagMonthsFromLabel(resolveDriverLagLabel(driver));
136
137
  const series = driver.normalized_series ?? {};
137
138
  const normalized_series =
@@ -546,12 +546,24 @@ export function ChatProvider({
546
546
  );
547
547
  }
548
548
 
549
+ let progressMessageId: string | undefined;
550
+ if (typeof message !== 'string' && message.systemProgressLabel) {
551
+ progressMessageId = addMessage(
552
+ scopeId,
553
+ targetChatId,
554
+ MessageRole.SYSTEM,
555
+ message.systemProgressLabel,
556
+ { inProgress: true },
557
+ );
558
+ }
559
+
549
560
  const pendingChatSessionId = targetChatId;
550
561
  beginOutboundPending(scopeId, pendingChatSessionId);
562
+ let effectiveSessionId = pendingChatSessionId;
551
563
  try {
552
564
  const data = await sendChatMessageFn(apiPayload, pendingChatSessionId);
553
565
 
554
- const effectiveSessionId =
566
+ effectiveSessionId =
555
567
  data.session_id && data.session_id !== pendingChatSessionId
556
568
  ? data.session_id
557
569
  : pendingChatSessionId;
@@ -573,6 +585,11 @@ export function ChatProvider({
573
585
  setCurrentChatId(scopeId, data.session_id);
574
586
  }
575
587
 
588
+ if (progressMessageId) {
589
+ removeMessageById(scopeId, effectiveSessionId, progressMessageId);
590
+ progressMessageId = undefined;
591
+ }
592
+
576
593
  addMessage(
577
594
  scopeId,
578
595
  effectiveSessionId,
@@ -582,6 +599,10 @@ export function ChatProvider({
582
599
 
583
600
  return { response: data.response, sessionId: effectiveSessionId };
584
601
  } catch (error) {
602
+ if (progressMessageId) {
603
+ removeMessageById(scopeId, effectiveSessionId, progressMessageId);
604
+ }
605
+
585
606
  const errorMessage =
586
607
  error instanceof Error
587
608
  ? error.message
@@ -589,7 +610,7 @@ export function ChatProvider({
589
610
 
590
611
  addMessage(
591
612
  scopeId,
592
- pendingChatSessionId,
613
+ effectiveSessionId,
593
614
  MessageRole.ASSISTANT,
594
615
  errorMessage,
595
616
  );
@@ -600,6 +621,7 @@ export function ChatProvider({
600
621
  },
601
622
  [
602
623
  addMessage,
624
+ removeMessageById,
603
625
  beginOutboundPending,
604
626
  endOutboundPending,
605
627
  getCurrentChatId,