@xinghunm/ai-chat 1.4.1 → 1.4.3

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/index.d.mts CHANGED
@@ -738,6 +738,7 @@ interface ChatState {
738
738
  sessionMessageLoadStatusBySession: Record<string, ChatSessionMessageLoadStatus>;
739
739
  sessionMessageLoadErrorBySession: Record<string, string | null>;
740
740
  historyMessagePaginationBySession: Record<string, ChatHistoryMessagePaginationState>;
741
+ timelineAnchorStateBySession: Record<string, Record<string, ChatTimelineAnchorStateSnapshot>>;
741
742
  }
742
743
  interface ChatHistoryMessagePaginationState {
743
744
  previousCursor: string | null;
@@ -745,6 +746,11 @@ interface ChatHistoryMessagePaginationState {
745
746
  isLoadingPrevious: boolean;
746
747
  error: string | null;
747
748
  }
749
+ interface ChatTimelineAnchorStateSnapshot {
750
+ previousBlockKeys: string[];
751
+ timelineBlockAnchors: Record<string, number>;
752
+ visibleTimelineBlockKeys: Record<string, true>;
753
+ }
748
754
  interface ChatActions {
749
755
  createSession: (session: ChatSession) => void;
750
756
  startNewChat: () => void;
@@ -758,6 +764,7 @@ interface ChatActions {
758
764
  prependHistorySessionMessagesPage: (sessionId: string, page: ChatHistorySessionMessagesPage) => void;
759
765
  setHistorySessionPreviousMessagesLoadStatus: (sessionId: string, isLoading: boolean, error?: string | null) => void;
760
766
  setHistorySessionMessageLoadStatus: (sessionId: string, status: ChatSessionMessageLoadStatus, error?: string | null) => void;
767
+ setTimelineAnchorState: (sessionId: string, messageId: string, snapshot: ChatTimelineAnchorStateSnapshot) => void;
761
768
  startStreamingMessage: (sessionId: string, message: ChatMessage) => void;
762
769
  updateStreamingMessage: (sessionId: string, content: string) => void;
763
770
  patchStreamingMessage: (sessionId: string, patch: ChatStreamMessagePatch) => void;
package/dist/index.d.ts CHANGED
@@ -738,6 +738,7 @@ interface ChatState {
738
738
  sessionMessageLoadStatusBySession: Record<string, ChatSessionMessageLoadStatus>;
739
739
  sessionMessageLoadErrorBySession: Record<string, string | null>;
740
740
  historyMessagePaginationBySession: Record<string, ChatHistoryMessagePaginationState>;
741
+ timelineAnchorStateBySession: Record<string, Record<string, ChatTimelineAnchorStateSnapshot>>;
741
742
  }
742
743
  interface ChatHistoryMessagePaginationState {
743
744
  previousCursor: string | null;
@@ -745,6 +746,11 @@ interface ChatHistoryMessagePaginationState {
745
746
  isLoadingPrevious: boolean;
746
747
  error: string | null;
747
748
  }
749
+ interface ChatTimelineAnchorStateSnapshot {
750
+ previousBlockKeys: string[];
751
+ timelineBlockAnchors: Record<string, number>;
752
+ visibleTimelineBlockKeys: Record<string, true>;
753
+ }
748
754
  interface ChatActions {
749
755
  createSession: (session: ChatSession) => void;
750
756
  startNewChat: () => void;
@@ -758,6 +764,7 @@ interface ChatActions {
758
764
  prependHistorySessionMessagesPage: (sessionId: string, page: ChatHistorySessionMessagesPage) => void;
759
765
  setHistorySessionPreviousMessagesLoadStatus: (sessionId: string, isLoading: boolean, error?: string | null) => void;
760
766
  setHistorySessionMessageLoadStatus: (sessionId: string, status: ChatSessionMessageLoadStatus, error?: string | null) => void;
767
+ setTimelineAnchorState: (sessionId: string, messageId: string, snapshot: ChatTimelineAnchorStateSnapshot) => void;
761
768
  startStreamingMessage: (sessionId: string, message: ChatMessage) => void;
762
769
  updateStreamingMessage: (sessionId: string, content: string) => void;
763
770
  patchStreamingMessage: (sessionId: string, patch: ChatStreamMessagePatch) => void;
package/dist/index.js CHANGED
@@ -157,6 +157,18 @@ var resolveSessionTitleFromMessage = (message) => {
157
157
  }
158
158
  return DEFAULT_CHAT_SESSION_TITLE;
159
159
  };
160
+ var areStringArraysEqual = (left, right) => left.length === right.length && left.every((value, index3) => value === right[index3]);
161
+ var areNumberRecordsEqual = (left, right) => {
162
+ const leftKeys = Object.keys(left);
163
+ const rightKeys = Object.keys(right);
164
+ return leftKeys.length === rightKeys.length && leftKeys.every((key) => right[key] === left[key]);
165
+ };
166
+ var areTrueRecordsEqual = (left, right) => {
167
+ const leftKeys = Object.keys(left);
168
+ const rightKeys = Object.keys(right);
169
+ return leftKeys.length === rightKeys.length && leftKeys.every((key) => right[key] === true);
170
+ };
171
+ var isSameTimelineAnchorSnapshot = (current, next) => current !== void 0 && areStringArraysEqual(current.previousBlockKeys, next.previousBlockKeys) && areNumberRecordsEqual(current.timelineBlockAnchors, next.timelineBlockAnchors) && areTrueRecordsEqual(current.visibleTimelineBlockKeys, next.visibleTimelineBlockKeys);
160
172
  var mergeStreamingBlocks = (existingBlocks, incomingBlocks) => {
161
173
  const nextBlocks = [...existingBlocks ?? []];
162
174
  incomingBlocks.forEach((incomingBlock) => {
@@ -249,6 +261,7 @@ var createChatStore = (initialState) => (0, import_vanilla.createStore)((set, ge
249
261
  sessionMessageLoadStatusBySession: {},
250
262
  sessionMessageLoadErrorBySession: {},
251
263
  historyMessagePaginationBySession: {},
264
+ timelineAnchorStateBySession: {},
252
265
  // ---- Session management ------------------------------------------------
253
266
  createSession: (session) => {
254
267
  const state = get();
@@ -267,6 +280,9 @@ var createChatStore = (initialState) => (0, import_vanilla.createStore)((set, ge
267
280
  const nextHistoryMessagePaginationBySession = {
268
281
  ...state.historyMessagePaginationBySession
269
282
  };
283
+ const nextTimelineAnchorStateBySession = {
284
+ ...state.timelineAnchorStateBySession
285
+ };
270
286
  const sid = session.sessionId;
271
287
  if (nextMessagesBySession[sid] === void 0)
272
288
  nextMessagesBySession[sid] = [];
@@ -294,7 +310,8 @@ var createChatStore = (initialState) => (0, import_vanilla.createStore)((set, ge
294
310
  isStoppingBySession: nextIsStoppingBySession,
295
311
  sessionMessageLoadStatusBySession: nextSessionMessageLoadStatusBySession,
296
312
  sessionMessageLoadErrorBySession: nextSessionMessageLoadErrorBySession,
297
- historyMessagePaginationBySession: nextHistoryMessagePaginationBySession
313
+ historyMessagePaginationBySession: nextHistoryMessagePaginationBySession,
314
+ timelineAnchorStateBySession: nextTimelineAnchorStateBySession
298
315
  });
299
316
  },
300
317
  startNewChat: () => {
@@ -364,10 +381,17 @@ var createChatStore = (initialState) => (0, import_vanilla.createStore)((set, ge
364
381
  const nextHistoryMessagePaginationBySession = {
365
382
  ...state.historyMessagePaginationBySession
366
383
  };
384
+ const nextTimelineAnchorStateBySession = {
385
+ ...state.timelineAnchorStateBySession
386
+ };
367
387
  if (previousSessionId in nextHistoryMessagePaginationBySession) {
368
388
  nextHistoryMessagePaginationBySession[nextSessionId] = nextHistoryMessagePaginationBySession[previousSessionId] ?? createHistoryMessagePaginationState();
369
389
  delete nextHistoryMessagePaginationBySession[previousSessionId];
370
390
  }
391
+ if (previousSessionId in nextTimelineAnchorStateBySession) {
392
+ nextTimelineAnchorStateBySession[nextSessionId] = nextTimelineAnchorStateBySession[previousSessionId] ?? {};
393
+ delete nextTimelineAnchorStateBySession[previousSessionId];
394
+ }
371
395
  const nextActiveSessionId = state.activeSessionId === previousSessionId ? nextSessionId : state.activeSessionId;
372
396
  set({
373
397
  sessions: nextSessions,
@@ -379,6 +403,7 @@ var createChatStore = (initialState) => (0, import_vanilla.createStore)((set, ge
379
403
  sessionMessageLoadStatusBySession: nextSessionMessageLoadStatusBySession,
380
404
  sessionMessageLoadErrorBySession: nextSessionMessageLoadErrorBySession,
381
405
  historyMessagePaginationBySession: nextHistoryMessagePaginationBySession,
406
+ timelineAnchorStateBySession: nextTimelineAnchorStateBySession,
382
407
  activeSessionId: nextActiveSessionId
383
408
  });
384
409
  },
@@ -387,13 +412,24 @@ var createChatStore = (initialState) => (0, import_vanilla.createStore)((set, ge
387
412
  },
388
413
  hydrateHistorySessions: (sessions) => {
389
414
  const state = get();
390
- const localSessions = state.sessions.filter(
415
+ const draftSessions = state.sessions.filter(
391
416
  (session) => isDraftChatSessionId(session.sessionId)
392
417
  );
393
- const localSessionIds = new Set(localSessions.map((session) => session.sessionId));
418
+ const activePersistedSession = state.sessions.find(
419
+ (session) => session.sessionId === state.activeSessionId && !isDraftChatSessionId(session.sessionId)
420
+ );
421
+ const hasActiveInIncoming = sessions.some(
422
+ (session) => session.sessionId === state.activeSessionId
423
+ );
424
+ const keepActiveSession = activePersistedSession && !hasActiveInIncoming ? [activePersistedSession] : [];
425
+ const excludeIds = /* @__PURE__ */ new Set([
426
+ ...draftSessions.map((s) => s.sessionId),
427
+ ...keepActiveSession.map((s) => s.sessionId)
428
+ ]);
394
429
  const nextSessions = [
395
- ...localSessions,
396
- ...sessions.filter((session) => !localSessionIds.has(session.sessionId))
430
+ ...draftSessions,
431
+ ...sessions.filter((session) => !excludeIds.has(session.sessionId)),
432
+ ...keepActiveSession
397
433
  ];
398
434
  const nextMessagesBySession = { ...state.messagesBySession };
399
435
  const nextErrorBySession = { ...state.errorBySession };
@@ -408,6 +444,9 @@ var createChatStore = (initialState) => (0, import_vanilla.createStore)((set, ge
408
444
  const nextHistoryMessagePaginationBySession = {
409
445
  ...state.historyMessagePaginationBySession
410
446
  };
447
+ const nextTimelineAnchorStateBySession = {
448
+ ...state.timelineAnchorStateBySession
449
+ };
411
450
  nextSessions.forEach((session) => {
412
451
  const sid = session.sessionId;
413
452
  if (nextErrorBySession[sid] === void 0)
@@ -434,7 +473,8 @@ var createChatStore = (initialState) => (0, import_vanilla.createStore)((set, ge
434
473
  isStoppingBySession: nextIsStoppingBySession,
435
474
  sessionMessageLoadStatusBySession: nextSessionMessageLoadStatusBySession,
436
475
  sessionMessageLoadErrorBySession: nextSessionMessageLoadErrorBySession,
437
- historyMessagePaginationBySession: nextHistoryMessagePaginationBySession
476
+ historyMessagePaginationBySession: nextHistoryMessagePaginationBySession,
477
+ timelineAnchorStateBySession: nextTimelineAnchorStateBySession
438
478
  });
439
479
  },
440
480
  // ---- Message operations ------------------------------------------------
@@ -549,6 +589,23 @@ var createChatStore = (initialState) => (0, import_vanilla.createStore)((set, ge
549
589
  }
550
590
  });
551
591
  },
592
+ setTimelineAnchorState: (sessionId, messageId, snapshot) => {
593
+ const state = get();
594
+ const currentSessionSnapshots = state.timelineAnchorStateBySession[sessionId] ?? {};
595
+ const currentSnapshot = currentSessionSnapshots[messageId];
596
+ if (isSameTimelineAnchorSnapshot(currentSnapshot, snapshot)) {
597
+ return;
598
+ }
599
+ set({
600
+ timelineAnchorStateBySession: {
601
+ ...state.timelineAnchorStateBySession,
602
+ [sessionId]: {
603
+ ...currentSessionSnapshots,
604
+ [messageId]: snapshot
605
+ }
606
+ }
607
+ });
608
+ },
552
609
  startStreamingMessage: (sessionId, message) => {
553
610
  const state = get();
554
611
  set({
@@ -1539,13 +1596,39 @@ var createTimelineAnchorState = ({
1539
1596
  timelineBlockAnchors: {},
1540
1597
  visibleTimelineBlockKeys: {}
1541
1598
  });
1599
+ var restoreTimelineAnchorState = ({
1600
+ messageId,
1601
+ currentBlockKeys,
1602
+ persistedState
1603
+ }) => {
1604
+ if (!persistedState) {
1605
+ return createTimelineAnchorState({ messageId, currentBlockKeys });
1606
+ }
1607
+ const currentBlockKeySet = new Set(currentBlockKeys);
1608
+ return {
1609
+ messageId,
1610
+ previousBlockKeys: persistedState.previousBlockKeys.filter(
1611
+ (blockKey) => currentBlockKeySet.has(blockKey)
1612
+ ),
1613
+ timelineBlockAnchors: Object.fromEntries(
1614
+ Object.entries(persistedState.timelineBlockAnchors).filter(
1615
+ ([blockKey]) => currentBlockKeySet.has(blockKey)
1616
+ )
1617
+ ),
1618
+ visibleTimelineBlockKeys: Object.fromEntries(
1619
+ Object.entries(persistedState.visibleTimelineBlockKeys).filter(
1620
+ ([blockKey]) => currentBlockKeySet.has(blockKey)
1621
+ )
1622
+ )
1623
+ };
1624
+ };
1542
1625
  var createInitialTimelineAnchorState = ({
1543
- messageId
1544
- }) => ({
1545
1626
  messageId,
1546
- previousBlockKeys: [],
1547
- timelineBlockAnchors: {},
1548
- visibleTimelineBlockKeys: {}
1627
+ currentBlockKeys,
1628
+ persistedState
1629
+ }) => ({
1630
+ ...restoreTimelineAnchorState({ messageId, currentBlockKeys, persistedState }),
1631
+ previousBlockKeys: persistedState?.previousBlockKeys ?? []
1549
1632
  });
1550
1633
  var timelineAnchorReducer = (state, action) => {
1551
1634
  switch (action.type) {
@@ -1553,7 +1636,7 @@ var timelineAnchorReducer = (state, action) => {
1553
1636
  if (state.messageId === action.messageId) {
1554
1637
  return state;
1555
1638
  }
1556
- return createTimelineAnchorState(action);
1639
+ return restoreTimelineAnchorState(action);
1557
1640
  case "sync-anchors": {
1558
1641
  const previousBlockKeys = new Set(state.previousBlockKeys);
1559
1642
  const nextAnchors = action.currentBlockKeys.reduce(
@@ -1622,10 +1705,15 @@ var useTimelineBlockAnchors = ({
1622
1705
  message,
1623
1706
  messageRenderOrder
1624
1707
  }) => {
1708
+ const { store } = useChatContext();
1625
1709
  const currentTimelineBlockKeys = (0, import_react6.useMemo)(
1626
1710
  () => blocks.map((block, index3) => getTimelineBlockKey(block, index3)).filter((blockKey) => Boolean(blockKey)),
1627
1711
  [blocks]
1628
1712
  );
1713
+ const persistedState = (0, import_react6.useMemo)(
1714
+ () => store.getState().timelineAnchorStateBySession[message.sessionId]?.[message.id],
1715
+ [message.id, message.sessionId, store]
1716
+ );
1629
1717
  const timelineTextStreamLength = (0, import_react6.useMemo)(
1630
1718
  () => getTimelineDisplayUnitCount(getTimelineTextStream(message.content, blocks)),
1631
1719
  [blocks, message.content]
@@ -1634,7 +1722,8 @@ var useTimelineBlockAnchors = ({
1634
1722
  timelineAnchorReducer,
1635
1723
  {
1636
1724
  messageId: message.id,
1637
- currentBlockKeys: currentTimelineBlockKeys
1725
+ currentBlockKeys: currentTimelineBlockKeys,
1726
+ persistedState
1638
1727
  },
1639
1728
  createInitialTimelineAnchorState
1640
1729
  );
@@ -1669,9 +1758,10 @@ var useTimelineBlockAnchors = ({
1669
1758
  dispatch({
1670
1759
  type: "reset-message",
1671
1760
  messageId: message.id,
1672
- currentBlockKeys: currentTimelineBlockKeys
1761
+ currentBlockKeys: currentTimelineBlockKeys,
1762
+ persistedState
1673
1763
  });
1674
- }, [currentTimelineBlockKeys, message.id]);
1764
+ }, [currentTimelineBlockKeys, message.id, persistedState]);
1675
1765
  (0, import_react6.useEffect)(() => {
1676
1766
  if (messageRenderOrder !== "timeline" || !isAssistantStreaming) {
1677
1767
  return;
@@ -1698,6 +1788,24 @@ var useTimelineBlockAnchors = ({
1698
1788
  effectiveTimelineBlockAnchors,
1699
1789
  messageRenderOrder
1700
1790
  ]);
1791
+ (0, import_react6.useEffect)(() => {
1792
+ if (messageRenderOrder !== "timeline") {
1793
+ return;
1794
+ }
1795
+ store.getState().setTimelineAnchorState(message.sessionId, message.id, {
1796
+ previousBlockKeys: state.previousBlockKeys,
1797
+ timelineBlockAnchors: state.timelineBlockAnchors,
1798
+ visibleTimelineBlockKeys: state.visibleTimelineBlockKeys
1799
+ });
1800
+ }, [
1801
+ message.id,
1802
+ message.sessionId,
1803
+ messageRenderOrder,
1804
+ state.previousBlockKeys,
1805
+ state.timelineBlockAnchors,
1806
+ state.visibleTimelineBlockKeys,
1807
+ store
1808
+ ]);
1701
1809
  return {
1702
1810
  timelineBlockAnchors: messageRenderOrder === "timeline" ? effectiveTimelineBlockAnchors : {},
1703
1811
  visibleTimelineBlockKeys: messageRenderOrder === "timeline" ? state.visibleTimelineBlockKeys : {}
@@ -3007,10 +3115,10 @@ var areExecutionProposalsEqual = (previousProposal, nextProposal) => previousPro
3007
3115
  (warning, index3) => warning === nextProposal.warnings?.[index3]
3008
3116
  );
3009
3117
  var areResultSummariesEqual = (previousSummary, nextSummary) => previousSummary.summaryId === nextSummary.summaryId && previousSummary.status === nextSummary.status && previousSummary.headline === nextSummary.headline && previousSummary.details.length === nextSummary.details.length && previousSummary.details.every((detail, index3) => detail === nextSummary.details[index3]);
3010
- var areStringArraysEqual = (previousValues, nextValues) => previousValues.length === nextValues.length && previousValues.every((value, index3) => value === nextValues[index3]);
3118
+ var areStringArraysEqual2 = (previousValues, nextValues) => previousValues.length === nextValues.length && previousValues.every((value, index3) => value === nextValues[index3]);
3011
3119
  var areQuestionAnswersEqual = (previousAnswer, nextAnswer) => {
3012
3120
  if (Array.isArray(previousAnswer) || Array.isArray(nextAnswer)) {
3013
- return Array.isArray(previousAnswer) && Array.isArray(nextAnswer) && areStringArraysEqual(previousAnswer, nextAnswer);
3121
+ return Array.isArray(previousAnswer) && Array.isArray(nextAnswer) && areStringArraysEqual2(previousAnswer, nextAnswer);
3014
3122
  }
3015
3123
  return previousAnswer === nextAnswer;
3016
3124
  };
@@ -3097,7 +3205,7 @@ var areMessageBlocksEqual = (previousBlocks, nextBlocks) => {
3097
3205
  }
3098
3206
  });
3099
3207
  };
3100
- var isSameMessage = (previousMessage, nextMessage, previousMode, nextMode, previousConfirmationSubmit, nextConfirmationSubmit, previousQuestionnaireSubmit, nextQuestionnaireSubmit, previousRenderMessageBlock, nextRenderMessageBlock) => previousMessage.id === nextMessage.id && previousMessage.sessionId === nextMessage.sessionId && previousMessage.role === nextMessage.role && previousMessage.content === nextMessage.content && areStringArraysEqual(previousMessage.skills ?? [], nextMessage.skills ?? []) && areMessageBlocksEqual(previousMessage.blocks, nextMessage.blocks) && previousMessage.localOnly === nextMessage.localOnly && areChatAttachmentsEqual(previousMessage.attachments, nextMessage.attachments) && previousMessage.status === nextMessage.status && previousMessage.createdAt === nextMessage.createdAt && previousMode === nextMode && previousConfirmationSubmit === nextConfirmationSubmit && previousQuestionnaireSubmit === nextQuestionnaireSubmit && previousRenderMessageBlock === nextRenderMessageBlock;
3208
+ var isSameMessage = (previousMessage, nextMessage, previousMode, nextMode, previousConfirmationSubmit, nextConfirmationSubmit, previousQuestionnaireSubmit, nextQuestionnaireSubmit, previousRenderMessageBlock, nextRenderMessageBlock) => previousMessage.id === nextMessage.id && previousMessage.sessionId === nextMessage.sessionId && previousMessage.role === nextMessage.role && previousMessage.content === nextMessage.content && areStringArraysEqual2(previousMessage.skills ?? [], nextMessage.skills ?? []) && areMessageBlocksEqual(previousMessage.blocks, nextMessage.blocks) && previousMessage.localOnly === nextMessage.localOnly && areChatAttachmentsEqual(previousMessage.attachments, nextMessage.attachments) && previousMessage.status === nextMessage.status && previousMessage.createdAt === nextMessage.createdAt && previousMode === nextMode && previousConfirmationSubmit === nextConfirmationSubmit && previousQuestionnaireSubmit === nextQuestionnaireSubmit && previousRenderMessageBlock === nextRenderMessageBlock;
3101
3209
  var ChatMessageItemView = ({
3102
3210
  message,
3103
3211
  mode = "agent",
@@ -9071,19 +9179,7 @@ var ChatConversationList = () => {
9071
9179
  isLoadingMoreRef.current = false;
9072
9180
  hasSeenLoadingMoreRef.current = false;
9073
9181
  }, [historySessionList?.sessions.length, historySessionList?.hasMore]);
9074
- const sessions = (0, import_react20.useMemo)(() => {
9075
- if (!historySessionList) {
9076
- return localSessions;
9077
- }
9078
- const persistedLocalSessions = localSessions.filter(
9079
- (session) => !isDraftChatSessionId(session.sessionId)
9080
- );
9081
- const localSessionIds = new Set(persistedLocalSessions.map((session) => session.sessionId));
9082
- return [
9083
- ...persistedLocalSessions,
9084
- ...historySessionList.sessions.filter((session) => !localSessionIds.has(session.sessionId))
9085
- ];
9086
- }, [historySessionList, localSessions]);
9182
+ const sessions = localSessions;
9087
9183
  const handleSessionListScroll = (event) => {
9088
9184
  if (!historySessionList?.hasMore || historySessionList.isLoading || !onLoadMoreSessions || isLoadingMoreRef.current) {
9089
9185
  return;
package/dist/index.mjs CHANGED
@@ -110,6 +110,18 @@ var resolveSessionTitleFromMessage = (message) => {
110
110
  }
111
111
  return DEFAULT_CHAT_SESSION_TITLE;
112
112
  };
113
+ var areStringArraysEqual = (left, right) => left.length === right.length && left.every((value, index3) => value === right[index3]);
114
+ var areNumberRecordsEqual = (left, right) => {
115
+ const leftKeys = Object.keys(left);
116
+ const rightKeys = Object.keys(right);
117
+ return leftKeys.length === rightKeys.length && leftKeys.every((key) => right[key] === left[key]);
118
+ };
119
+ var areTrueRecordsEqual = (left, right) => {
120
+ const leftKeys = Object.keys(left);
121
+ const rightKeys = Object.keys(right);
122
+ return leftKeys.length === rightKeys.length && leftKeys.every((key) => right[key] === true);
123
+ };
124
+ var isSameTimelineAnchorSnapshot = (current, next) => current !== void 0 && areStringArraysEqual(current.previousBlockKeys, next.previousBlockKeys) && areNumberRecordsEqual(current.timelineBlockAnchors, next.timelineBlockAnchors) && areTrueRecordsEqual(current.visibleTimelineBlockKeys, next.visibleTimelineBlockKeys);
113
125
  var mergeStreamingBlocks = (existingBlocks, incomingBlocks) => {
114
126
  const nextBlocks = [...existingBlocks ?? []];
115
127
  incomingBlocks.forEach((incomingBlock) => {
@@ -202,6 +214,7 @@ var createChatStore = (initialState) => createStore((set, get) => ({
202
214
  sessionMessageLoadStatusBySession: {},
203
215
  sessionMessageLoadErrorBySession: {},
204
216
  historyMessagePaginationBySession: {},
217
+ timelineAnchorStateBySession: {},
205
218
  // ---- Session management ------------------------------------------------
206
219
  createSession: (session) => {
207
220
  const state = get();
@@ -220,6 +233,9 @@ var createChatStore = (initialState) => createStore((set, get) => ({
220
233
  const nextHistoryMessagePaginationBySession = {
221
234
  ...state.historyMessagePaginationBySession
222
235
  };
236
+ const nextTimelineAnchorStateBySession = {
237
+ ...state.timelineAnchorStateBySession
238
+ };
223
239
  const sid = session.sessionId;
224
240
  if (nextMessagesBySession[sid] === void 0)
225
241
  nextMessagesBySession[sid] = [];
@@ -247,7 +263,8 @@ var createChatStore = (initialState) => createStore((set, get) => ({
247
263
  isStoppingBySession: nextIsStoppingBySession,
248
264
  sessionMessageLoadStatusBySession: nextSessionMessageLoadStatusBySession,
249
265
  sessionMessageLoadErrorBySession: nextSessionMessageLoadErrorBySession,
250
- historyMessagePaginationBySession: nextHistoryMessagePaginationBySession
266
+ historyMessagePaginationBySession: nextHistoryMessagePaginationBySession,
267
+ timelineAnchorStateBySession: nextTimelineAnchorStateBySession
251
268
  });
252
269
  },
253
270
  startNewChat: () => {
@@ -317,10 +334,17 @@ var createChatStore = (initialState) => createStore((set, get) => ({
317
334
  const nextHistoryMessagePaginationBySession = {
318
335
  ...state.historyMessagePaginationBySession
319
336
  };
337
+ const nextTimelineAnchorStateBySession = {
338
+ ...state.timelineAnchorStateBySession
339
+ };
320
340
  if (previousSessionId in nextHistoryMessagePaginationBySession) {
321
341
  nextHistoryMessagePaginationBySession[nextSessionId] = nextHistoryMessagePaginationBySession[previousSessionId] ?? createHistoryMessagePaginationState();
322
342
  delete nextHistoryMessagePaginationBySession[previousSessionId];
323
343
  }
344
+ if (previousSessionId in nextTimelineAnchorStateBySession) {
345
+ nextTimelineAnchorStateBySession[nextSessionId] = nextTimelineAnchorStateBySession[previousSessionId] ?? {};
346
+ delete nextTimelineAnchorStateBySession[previousSessionId];
347
+ }
324
348
  const nextActiveSessionId = state.activeSessionId === previousSessionId ? nextSessionId : state.activeSessionId;
325
349
  set({
326
350
  sessions: nextSessions,
@@ -332,6 +356,7 @@ var createChatStore = (initialState) => createStore((set, get) => ({
332
356
  sessionMessageLoadStatusBySession: nextSessionMessageLoadStatusBySession,
333
357
  sessionMessageLoadErrorBySession: nextSessionMessageLoadErrorBySession,
334
358
  historyMessagePaginationBySession: nextHistoryMessagePaginationBySession,
359
+ timelineAnchorStateBySession: nextTimelineAnchorStateBySession,
335
360
  activeSessionId: nextActiveSessionId
336
361
  });
337
362
  },
@@ -340,13 +365,24 @@ var createChatStore = (initialState) => createStore((set, get) => ({
340
365
  },
341
366
  hydrateHistorySessions: (sessions) => {
342
367
  const state = get();
343
- const localSessions = state.sessions.filter(
368
+ const draftSessions = state.sessions.filter(
344
369
  (session) => isDraftChatSessionId(session.sessionId)
345
370
  );
346
- const localSessionIds = new Set(localSessions.map((session) => session.sessionId));
371
+ const activePersistedSession = state.sessions.find(
372
+ (session) => session.sessionId === state.activeSessionId && !isDraftChatSessionId(session.sessionId)
373
+ );
374
+ const hasActiveInIncoming = sessions.some(
375
+ (session) => session.sessionId === state.activeSessionId
376
+ );
377
+ const keepActiveSession = activePersistedSession && !hasActiveInIncoming ? [activePersistedSession] : [];
378
+ const excludeIds = /* @__PURE__ */ new Set([
379
+ ...draftSessions.map((s) => s.sessionId),
380
+ ...keepActiveSession.map((s) => s.sessionId)
381
+ ]);
347
382
  const nextSessions = [
348
- ...localSessions,
349
- ...sessions.filter((session) => !localSessionIds.has(session.sessionId))
383
+ ...draftSessions,
384
+ ...sessions.filter((session) => !excludeIds.has(session.sessionId)),
385
+ ...keepActiveSession
350
386
  ];
351
387
  const nextMessagesBySession = { ...state.messagesBySession };
352
388
  const nextErrorBySession = { ...state.errorBySession };
@@ -361,6 +397,9 @@ var createChatStore = (initialState) => createStore((set, get) => ({
361
397
  const nextHistoryMessagePaginationBySession = {
362
398
  ...state.historyMessagePaginationBySession
363
399
  };
400
+ const nextTimelineAnchorStateBySession = {
401
+ ...state.timelineAnchorStateBySession
402
+ };
364
403
  nextSessions.forEach((session) => {
365
404
  const sid = session.sessionId;
366
405
  if (nextErrorBySession[sid] === void 0)
@@ -387,7 +426,8 @@ var createChatStore = (initialState) => createStore((set, get) => ({
387
426
  isStoppingBySession: nextIsStoppingBySession,
388
427
  sessionMessageLoadStatusBySession: nextSessionMessageLoadStatusBySession,
389
428
  sessionMessageLoadErrorBySession: nextSessionMessageLoadErrorBySession,
390
- historyMessagePaginationBySession: nextHistoryMessagePaginationBySession
429
+ historyMessagePaginationBySession: nextHistoryMessagePaginationBySession,
430
+ timelineAnchorStateBySession: nextTimelineAnchorStateBySession
391
431
  });
392
432
  },
393
433
  // ---- Message operations ------------------------------------------------
@@ -502,6 +542,23 @@ var createChatStore = (initialState) => createStore((set, get) => ({
502
542
  }
503
543
  });
504
544
  },
545
+ setTimelineAnchorState: (sessionId, messageId, snapshot) => {
546
+ const state = get();
547
+ const currentSessionSnapshots = state.timelineAnchorStateBySession[sessionId] ?? {};
548
+ const currentSnapshot = currentSessionSnapshots[messageId];
549
+ if (isSameTimelineAnchorSnapshot(currentSnapshot, snapshot)) {
550
+ return;
551
+ }
552
+ set({
553
+ timelineAnchorStateBySession: {
554
+ ...state.timelineAnchorStateBySession,
555
+ [sessionId]: {
556
+ ...currentSessionSnapshots,
557
+ [messageId]: snapshot
558
+ }
559
+ }
560
+ });
561
+ },
505
562
  startStreamingMessage: (sessionId, message) => {
506
563
  const state = get();
507
564
  set({
@@ -1492,13 +1549,39 @@ var createTimelineAnchorState = ({
1492
1549
  timelineBlockAnchors: {},
1493
1550
  visibleTimelineBlockKeys: {}
1494
1551
  });
1552
+ var restoreTimelineAnchorState = ({
1553
+ messageId,
1554
+ currentBlockKeys,
1555
+ persistedState
1556
+ }) => {
1557
+ if (!persistedState) {
1558
+ return createTimelineAnchorState({ messageId, currentBlockKeys });
1559
+ }
1560
+ const currentBlockKeySet = new Set(currentBlockKeys);
1561
+ return {
1562
+ messageId,
1563
+ previousBlockKeys: persistedState.previousBlockKeys.filter(
1564
+ (blockKey) => currentBlockKeySet.has(blockKey)
1565
+ ),
1566
+ timelineBlockAnchors: Object.fromEntries(
1567
+ Object.entries(persistedState.timelineBlockAnchors).filter(
1568
+ ([blockKey]) => currentBlockKeySet.has(blockKey)
1569
+ )
1570
+ ),
1571
+ visibleTimelineBlockKeys: Object.fromEntries(
1572
+ Object.entries(persistedState.visibleTimelineBlockKeys).filter(
1573
+ ([blockKey]) => currentBlockKeySet.has(blockKey)
1574
+ )
1575
+ )
1576
+ };
1577
+ };
1495
1578
  var createInitialTimelineAnchorState = ({
1496
- messageId
1497
- }) => ({
1498
1579
  messageId,
1499
- previousBlockKeys: [],
1500
- timelineBlockAnchors: {},
1501
- visibleTimelineBlockKeys: {}
1580
+ currentBlockKeys,
1581
+ persistedState
1582
+ }) => ({
1583
+ ...restoreTimelineAnchorState({ messageId, currentBlockKeys, persistedState }),
1584
+ previousBlockKeys: persistedState?.previousBlockKeys ?? []
1502
1585
  });
1503
1586
  var timelineAnchorReducer = (state, action) => {
1504
1587
  switch (action.type) {
@@ -1506,7 +1589,7 @@ var timelineAnchorReducer = (state, action) => {
1506
1589
  if (state.messageId === action.messageId) {
1507
1590
  return state;
1508
1591
  }
1509
- return createTimelineAnchorState(action);
1592
+ return restoreTimelineAnchorState(action);
1510
1593
  case "sync-anchors": {
1511
1594
  const previousBlockKeys = new Set(state.previousBlockKeys);
1512
1595
  const nextAnchors = action.currentBlockKeys.reduce(
@@ -1575,10 +1658,15 @@ var useTimelineBlockAnchors = ({
1575
1658
  message,
1576
1659
  messageRenderOrder
1577
1660
  }) => {
1661
+ const { store } = useChatContext();
1578
1662
  const currentTimelineBlockKeys = useMemo3(
1579
1663
  () => blocks.map((block, index3) => getTimelineBlockKey(block, index3)).filter((blockKey) => Boolean(blockKey)),
1580
1664
  [blocks]
1581
1665
  );
1666
+ const persistedState = useMemo3(
1667
+ () => store.getState().timelineAnchorStateBySession[message.sessionId]?.[message.id],
1668
+ [message.id, message.sessionId, store]
1669
+ );
1582
1670
  const timelineTextStreamLength = useMemo3(
1583
1671
  () => getTimelineDisplayUnitCount(getTimelineTextStream(message.content, blocks)),
1584
1672
  [blocks, message.content]
@@ -1587,7 +1675,8 @@ var useTimelineBlockAnchors = ({
1587
1675
  timelineAnchorReducer,
1588
1676
  {
1589
1677
  messageId: message.id,
1590
- currentBlockKeys: currentTimelineBlockKeys
1678
+ currentBlockKeys: currentTimelineBlockKeys,
1679
+ persistedState
1591
1680
  },
1592
1681
  createInitialTimelineAnchorState
1593
1682
  );
@@ -1622,9 +1711,10 @@ var useTimelineBlockAnchors = ({
1622
1711
  dispatch({
1623
1712
  type: "reset-message",
1624
1713
  messageId: message.id,
1625
- currentBlockKeys: currentTimelineBlockKeys
1714
+ currentBlockKeys: currentTimelineBlockKeys,
1715
+ persistedState
1626
1716
  });
1627
- }, [currentTimelineBlockKeys, message.id]);
1717
+ }, [currentTimelineBlockKeys, message.id, persistedState]);
1628
1718
  useEffect3(() => {
1629
1719
  if (messageRenderOrder !== "timeline" || !isAssistantStreaming) {
1630
1720
  return;
@@ -1651,6 +1741,24 @@ var useTimelineBlockAnchors = ({
1651
1741
  effectiveTimelineBlockAnchors,
1652
1742
  messageRenderOrder
1653
1743
  ]);
1744
+ useEffect3(() => {
1745
+ if (messageRenderOrder !== "timeline") {
1746
+ return;
1747
+ }
1748
+ store.getState().setTimelineAnchorState(message.sessionId, message.id, {
1749
+ previousBlockKeys: state.previousBlockKeys,
1750
+ timelineBlockAnchors: state.timelineBlockAnchors,
1751
+ visibleTimelineBlockKeys: state.visibleTimelineBlockKeys
1752
+ });
1753
+ }, [
1754
+ message.id,
1755
+ message.sessionId,
1756
+ messageRenderOrder,
1757
+ state.previousBlockKeys,
1758
+ state.timelineBlockAnchors,
1759
+ state.visibleTimelineBlockKeys,
1760
+ store
1761
+ ]);
1654
1762
  return {
1655
1763
  timelineBlockAnchors: messageRenderOrder === "timeline" ? effectiveTimelineBlockAnchors : {},
1656
1764
  visibleTimelineBlockKeys: messageRenderOrder === "timeline" ? state.visibleTimelineBlockKeys : {}
@@ -2964,10 +3072,10 @@ var areExecutionProposalsEqual = (previousProposal, nextProposal) => previousPro
2964
3072
  (warning, index3) => warning === nextProposal.warnings?.[index3]
2965
3073
  );
2966
3074
  var areResultSummariesEqual = (previousSummary, nextSummary) => previousSummary.summaryId === nextSummary.summaryId && previousSummary.status === nextSummary.status && previousSummary.headline === nextSummary.headline && previousSummary.details.length === nextSummary.details.length && previousSummary.details.every((detail, index3) => detail === nextSummary.details[index3]);
2967
- var areStringArraysEqual = (previousValues, nextValues) => previousValues.length === nextValues.length && previousValues.every((value, index3) => value === nextValues[index3]);
3075
+ var areStringArraysEqual2 = (previousValues, nextValues) => previousValues.length === nextValues.length && previousValues.every((value, index3) => value === nextValues[index3]);
2968
3076
  var areQuestionAnswersEqual = (previousAnswer, nextAnswer) => {
2969
3077
  if (Array.isArray(previousAnswer) || Array.isArray(nextAnswer)) {
2970
- return Array.isArray(previousAnswer) && Array.isArray(nextAnswer) && areStringArraysEqual(previousAnswer, nextAnswer);
3078
+ return Array.isArray(previousAnswer) && Array.isArray(nextAnswer) && areStringArraysEqual2(previousAnswer, nextAnswer);
2971
3079
  }
2972
3080
  return previousAnswer === nextAnswer;
2973
3081
  };
@@ -3054,7 +3162,7 @@ var areMessageBlocksEqual = (previousBlocks, nextBlocks) => {
3054
3162
  }
3055
3163
  });
3056
3164
  };
3057
- var isSameMessage = (previousMessage, nextMessage, previousMode, nextMode, previousConfirmationSubmit, nextConfirmationSubmit, previousQuestionnaireSubmit, nextQuestionnaireSubmit, previousRenderMessageBlock, nextRenderMessageBlock) => previousMessage.id === nextMessage.id && previousMessage.sessionId === nextMessage.sessionId && previousMessage.role === nextMessage.role && previousMessage.content === nextMessage.content && areStringArraysEqual(previousMessage.skills ?? [], nextMessage.skills ?? []) && areMessageBlocksEqual(previousMessage.blocks, nextMessage.blocks) && previousMessage.localOnly === nextMessage.localOnly && areChatAttachmentsEqual(previousMessage.attachments, nextMessage.attachments) && previousMessage.status === nextMessage.status && previousMessage.createdAt === nextMessage.createdAt && previousMode === nextMode && previousConfirmationSubmit === nextConfirmationSubmit && previousQuestionnaireSubmit === nextQuestionnaireSubmit && previousRenderMessageBlock === nextRenderMessageBlock;
3165
+ var isSameMessage = (previousMessage, nextMessage, previousMode, nextMode, previousConfirmationSubmit, nextConfirmationSubmit, previousQuestionnaireSubmit, nextQuestionnaireSubmit, previousRenderMessageBlock, nextRenderMessageBlock) => previousMessage.id === nextMessage.id && previousMessage.sessionId === nextMessage.sessionId && previousMessage.role === nextMessage.role && previousMessage.content === nextMessage.content && areStringArraysEqual2(previousMessage.skills ?? [], nextMessage.skills ?? []) && areMessageBlocksEqual(previousMessage.blocks, nextMessage.blocks) && previousMessage.localOnly === nextMessage.localOnly && areChatAttachmentsEqual(previousMessage.attachments, nextMessage.attachments) && previousMessage.status === nextMessage.status && previousMessage.createdAt === nextMessage.createdAt && previousMode === nextMode && previousConfirmationSubmit === nextConfirmationSubmit && previousQuestionnaireSubmit === nextQuestionnaireSubmit && previousRenderMessageBlock === nextRenderMessageBlock;
3058
3166
  var ChatMessageItemView = ({
3059
3167
  message,
3060
3168
  mode = "agent",
@@ -8906,7 +9014,7 @@ var SkillButton = styled14.button`
8906
9014
  `;
8907
9015
 
8908
9016
  // src/components/chat-conversation-list/index.tsx
8909
- import { useEffect as useEffect10, useMemo as useMemo7, useRef as useRef12 } from "react";
9017
+ import { useEffect as useEffect10, useRef as useRef12 } from "react";
8910
9018
  import styled16 from "@emotion/styled";
8911
9019
 
8912
9020
  // src/components/chat-conversation-list/components/chat-session-item.tsx
@@ -9028,19 +9136,7 @@ var ChatConversationList = () => {
9028
9136
  isLoadingMoreRef.current = false;
9029
9137
  hasSeenLoadingMoreRef.current = false;
9030
9138
  }, [historySessionList?.sessions.length, historySessionList?.hasMore]);
9031
- const sessions = useMemo7(() => {
9032
- if (!historySessionList) {
9033
- return localSessions;
9034
- }
9035
- const persistedLocalSessions = localSessions.filter(
9036
- (session) => !isDraftChatSessionId(session.sessionId)
9037
- );
9038
- const localSessionIds = new Set(persistedLocalSessions.map((session) => session.sessionId));
9039
- return [
9040
- ...persistedLocalSessions,
9041
- ...historySessionList.sessions.filter((session) => !localSessionIds.has(session.sessionId))
9042
- ];
9043
- }, [historySessionList, localSessions]);
9139
+ const sessions = localSessions;
9044
9140
  const handleSessionListScroll = (event) => {
9045
9141
  if (!historySessionList?.hasMore || historySessionList.isLoading || !onLoadMoreSessions || isLoadingMoreRef.current) {
9046
9142
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xinghunm/ai-chat",
3
- "version": "1.4.1",
3
+ "version": "1.4.3",
4
4
  "description": "AI chat React component library",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -19,7 +19,7 @@
19
19
  "peerDependencies": {
20
20
  "@emotion/react": ">=11",
21
21
  "@emotion/styled": ">=11",
22
- "@xinghunm/compass-ui": "0.8.3",
22
+ "@xinghunm/compass-ui": "0.9.0",
23
23
  "axios": ">=1.0",
24
24
  "react": ">=18",
25
25
  "react-dom": ">=18",