@xinghunm/ai-chat 1.4.2 → 1.4.4

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,8 @@ 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>>;
742
+ newlyCreatedSessionIds: string[];
741
743
  }
742
744
  interface ChatHistoryMessagePaginationState {
743
745
  previousCursor: string | null;
@@ -745,6 +747,11 @@ interface ChatHistoryMessagePaginationState {
745
747
  isLoadingPrevious: boolean;
746
748
  error: string | null;
747
749
  }
750
+ interface ChatTimelineAnchorStateSnapshot {
751
+ previousBlockKeys: string[];
752
+ timelineBlockAnchors: Record<string, number>;
753
+ visibleTimelineBlockKeys: Record<string, true>;
754
+ }
748
755
  interface ChatActions {
749
756
  createSession: (session: ChatSession) => void;
750
757
  startNewChat: () => void;
@@ -758,6 +765,7 @@ interface ChatActions {
758
765
  prependHistorySessionMessagesPage: (sessionId: string, page: ChatHistorySessionMessagesPage) => void;
759
766
  setHistorySessionPreviousMessagesLoadStatus: (sessionId: string, isLoading: boolean, error?: string | null) => void;
760
767
  setHistorySessionMessageLoadStatus: (sessionId: string, status: ChatSessionMessageLoadStatus, error?: string | null) => void;
768
+ setTimelineAnchorState: (sessionId: string, messageId: string, snapshot: ChatTimelineAnchorStateSnapshot) => void;
761
769
  startStreamingMessage: (sessionId: string, message: ChatMessage) => void;
762
770
  updateStreamingMessage: (sessionId: string, content: string) => void;
763
771
  patchStreamingMessage: (sessionId: string, patch: ChatStreamMessagePatch) => void;
package/dist/index.d.ts CHANGED
@@ -738,6 +738,8 @@ 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>>;
742
+ newlyCreatedSessionIds: string[];
741
743
  }
742
744
  interface ChatHistoryMessagePaginationState {
743
745
  previousCursor: string | null;
@@ -745,6 +747,11 @@ interface ChatHistoryMessagePaginationState {
745
747
  isLoadingPrevious: boolean;
746
748
  error: string | null;
747
749
  }
750
+ interface ChatTimelineAnchorStateSnapshot {
751
+ previousBlockKeys: string[];
752
+ timelineBlockAnchors: Record<string, number>;
753
+ visibleTimelineBlockKeys: Record<string, true>;
754
+ }
748
755
  interface ChatActions {
749
756
  createSession: (session: ChatSession) => void;
750
757
  startNewChat: () => void;
@@ -758,6 +765,7 @@ interface ChatActions {
758
765
  prependHistorySessionMessagesPage: (sessionId: string, page: ChatHistorySessionMessagesPage) => void;
759
766
  setHistorySessionPreviousMessagesLoadStatus: (sessionId: string, isLoading: boolean, error?: string | null) => void;
760
767
  setHistorySessionMessageLoadStatus: (sessionId: string, status: ChatSessionMessageLoadStatus, error?: string | null) => void;
768
+ setTimelineAnchorState: (sessionId: string, messageId: string, snapshot: ChatTimelineAnchorStateSnapshot) => void;
761
769
  startStreamingMessage: (sessionId: string, message: ChatMessage) => void;
762
770
  updateStreamingMessage: (sessionId: string, content: string) => void;
763
771
  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,8 @@ var createChatStore = (initialState) => (0, import_vanilla.createStore)((set, ge
249
261
  sessionMessageLoadStatusBySession: {},
250
262
  sessionMessageLoadErrorBySession: {},
251
263
  historyMessagePaginationBySession: {},
264
+ timelineAnchorStateBySession: {},
265
+ newlyCreatedSessionIds: [],
252
266
  // ---- Session management ------------------------------------------------
253
267
  createSession: (session) => {
254
268
  const state = get();
@@ -267,6 +281,9 @@ var createChatStore = (initialState) => (0, import_vanilla.createStore)((set, ge
267
281
  const nextHistoryMessagePaginationBySession = {
268
282
  ...state.historyMessagePaginationBySession
269
283
  };
284
+ const nextTimelineAnchorStateBySession = {
285
+ ...state.timelineAnchorStateBySession
286
+ };
270
287
  const sid = session.sessionId;
271
288
  if (nextMessagesBySession[sid] === void 0)
272
289
  nextMessagesBySession[sid] = [];
@@ -294,7 +311,8 @@ var createChatStore = (initialState) => (0, import_vanilla.createStore)((set, ge
294
311
  isStoppingBySession: nextIsStoppingBySession,
295
312
  sessionMessageLoadStatusBySession: nextSessionMessageLoadStatusBySession,
296
313
  sessionMessageLoadErrorBySession: nextSessionMessageLoadErrorBySession,
297
- historyMessagePaginationBySession: nextHistoryMessagePaginationBySession
314
+ historyMessagePaginationBySession: nextHistoryMessagePaginationBySession,
315
+ timelineAnchorStateBySession: nextTimelineAnchorStateBySession
298
316
  });
299
317
  },
300
318
  startNewChat: () => {
@@ -364,11 +382,19 @@ var createChatStore = (initialState) => (0, import_vanilla.createStore)((set, ge
364
382
  const nextHistoryMessagePaginationBySession = {
365
383
  ...state.historyMessagePaginationBySession
366
384
  };
385
+ const nextTimelineAnchorStateBySession = {
386
+ ...state.timelineAnchorStateBySession
387
+ };
367
388
  if (previousSessionId in nextHistoryMessagePaginationBySession) {
368
389
  nextHistoryMessagePaginationBySession[nextSessionId] = nextHistoryMessagePaginationBySession[previousSessionId] ?? createHistoryMessagePaginationState();
369
390
  delete nextHistoryMessagePaginationBySession[previousSessionId];
370
391
  }
392
+ if (previousSessionId in nextTimelineAnchorStateBySession) {
393
+ nextTimelineAnchorStateBySession[nextSessionId] = nextTimelineAnchorStateBySession[previousSessionId] ?? {};
394
+ delete nextTimelineAnchorStateBySession[previousSessionId];
395
+ }
371
396
  const nextActiveSessionId = state.activeSessionId === previousSessionId ? nextSessionId : state.activeSessionId;
397
+ const nextNewlyCreatedSessionIds = state.newlyCreatedSessionIds.includes(nextSessionId) ? state.newlyCreatedSessionIds : [...state.newlyCreatedSessionIds.filter((id) => id !== previousSessionId), nextSessionId];
372
398
  set({
373
399
  sessions: nextSessions,
374
400
  messagesBySession: nextMessagesBySession,
@@ -379,7 +405,9 @@ var createChatStore = (initialState) => (0, import_vanilla.createStore)((set, ge
379
405
  sessionMessageLoadStatusBySession: nextSessionMessageLoadStatusBySession,
380
406
  sessionMessageLoadErrorBySession: nextSessionMessageLoadErrorBySession,
381
407
  historyMessagePaginationBySession: nextHistoryMessagePaginationBySession,
382
- activeSessionId: nextActiveSessionId
408
+ timelineAnchorStateBySession: nextTimelineAnchorStateBySession,
409
+ activeSessionId: nextActiveSessionId,
410
+ newlyCreatedSessionIds: nextNewlyCreatedSessionIds
383
411
  });
384
412
  },
385
413
  setPreferredMode: (mode) => {
@@ -396,15 +424,28 @@ var createChatStore = (initialState) => (0, import_vanilla.createStore)((set, ge
396
424
  const hasActiveInIncoming = sessions.some(
397
425
  (session) => session.sessionId === state.activeSessionId
398
426
  );
399
- const keepActiveSession = activePersistedSession && !hasActiveInIncoming ? [activePersistedSession] : [];
427
+ let prependActiveSession = [];
428
+ let appendActiveSession = [];
429
+ if (activePersistedSession && !hasActiveInIncoming) {
430
+ const isNewlyCreated = state.newlyCreatedSessionIds.includes(
431
+ activePersistedSession.sessionId
432
+ );
433
+ if (isNewlyCreated) {
434
+ prependActiveSession = [activePersistedSession];
435
+ } else {
436
+ appendActiveSession = [activePersistedSession];
437
+ }
438
+ }
400
439
  const excludeIds = /* @__PURE__ */ new Set([
401
440
  ...draftSessions.map((s) => s.sessionId),
402
- ...keepActiveSession.map((s) => s.sessionId)
441
+ ...prependActiveSession.map((s) => s.sessionId),
442
+ ...appendActiveSession.map((s) => s.sessionId)
403
443
  ]);
404
444
  const nextSessions = [
405
445
  ...draftSessions,
446
+ ...prependActiveSession,
406
447
  ...sessions.filter((session) => !excludeIds.has(session.sessionId)),
407
- ...keepActiveSession
448
+ ...appendActiveSession
408
449
  ];
409
450
  const nextMessagesBySession = { ...state.messagesBySession };
410
451
  const nextErrorBySession = { ...state.errorBySession };
@@ -419,6 +460,9 @@ var createChatStore = (initialState) => (0, import_vanilla.createStore)((set, ge
419
460
  const nextHistoryMessagePaginationBySession = {
420
461
  ...state.historyMessagePaginationBySession
421
462
  };
463
+ const nextTimelineAnchorStateBySession = {
464
+ ...state.timelineAnchorStateBySession
465
+ };
422
466
  nextSessions.forEach((session) => {
423
467
  const sid = session.sessionId;
424
468
  if (nextErrorBySession[sid] === void 0)
@@ -437,6 +481,10 @@ var createChatStore = (initialState) => (0, import_vanilla.createStore)((set, ge
437
481
  nextHistoryMessagePaginationBySession[sid] = createHistoryMessagePaginationState();
438
482
  }
439
483
  });
484
+ const incomingSessionIds = new Set(sessions.map((s) => s.sessionId));
485
+ const nextNewlyCreatedSessionIds = state.newlyCreatedSessionIds.filter(
486
+ (id) => !incomingSessionIds.has(id)
487
+ );
440
488
  set({
441
489
  sessions: nextSessions,
442
490
  messagesBySession: nextMessagesBySession,
@@ -445,7 +493,9 @@ var createChatStore = (initialState) => (0, import_vanilla.createStore)((set, ge
445
493
  isStoppingBySession: nextIsStoppingBySession,
446
494
  sessionMessageLoadStatusBySession: nextSessionMessageLoadStatusBySession,
447
495
  sessionMessageLoadErrorBySession: nextSessionMessageLoadErrorBySession,
448
- historyMessagePaginationBySession: nextHistoryMessagePaginationBySession
496
+ historyMessagePaginationBySession: nextHistoryMessagePaginationBySession,
497
+ timelineAnchorStateBySession: nextTimelineAnchorStateBySession,
498
+ newlyCreatedSessionIds: nextNewlyCreatedSessionIds
449
499
  });
450
500
  },
451
501
  // ---- Message operations ------------------------------------------------
@@ -560,6 +610,23 @@ var createChatStore = (initialState) => (0, import_vanilla.createStore)((set, ge
560
610
  }
561
611
  });
562
612
  },
613
+ setTimelineAnchorState: (sessionId, messageId, snapshot) => {
614
+ const state = get();
615
+ const currentSessionSnapshots = state.timelineAnchorStateBySession[sessionId] ?? {};
616
+ const currentSnapshot = currentSessionSnapshots[messageId];
617
+ if (isSameTimelineAnchorSnapshot(currentSnapshot, snapshot)) {
618
+ return;
619
+ }
620
+ set({
621
+ timelineAnchorStateBySession: {
622
+ ...state.timelineAnchorStateBySession,
623
+ [sessionId]: {
624
+ ...currentSessionSnapshots,
625
+ [messageId]: snapshot
626
+ }
627
+ }
628
+ });
629
+ },
563
630
  startStreamingMessage: (sessionId, message) => {
564
631
  const state = get();
565
632
  set({
@@ -1550,13 +1617,39 @@ var createTimelineAnchorState = ({
1550
1617
  timelineBlockAnchors: {},
1551
1618
  visibleTimelineBlockKeys: {}
1552
1619
  });
1620
+ var restoreTimelineAnchorState = ({
1621
+ messageId,
1622
+ currentBlockKeys,
1623
+ persistedState
1624
+ }) => {
1625
+ if (!persistedState) {
1626
+ return createTimelineAnchorState({ messageId, currentBlockKeys });
1627
+ }
1628
+ const currentBlockKeySet = new Set(currentBlockKeys);
1629
+ return {
1630
+ messageId,
1631
+ previousBlockKeys: persistedState.previousBlockKeys.filter(
1632
+ (blockKey) => currentBlockKeySet.has(blockKey)
1633
+ ),
1634
+ timelineBlockAnchors: Object.fromEntries(
1635
+ Object.entries(persistedState.timelineBlockAnchors).filter(
1636
+ ([blockKey]) => currentBlockKeySet.has(blockKey)
1637
+ )
1638
+ ),
1639
+ visibleTimelineBlockKeys: Object.fromEntries(
1640
+ Object.entries(persistedState.visibleTimelineBlockKeys).filter(
1641
+ ([blockKey]) => currentBlockKeySet.has(blockKey)
1642
+ )
1643
+ )
1644
+ };
1645
+ };
1553
1646
  var createInitialTimelineAnchorState = ({
1554
- messageId
1555
- }) => ({
1556
1647
  messageId,
1557
- previousBlockKeys: [],
1558
- timelineBlockAnchors: {},
1559
- visibleTimelineBlockKeys: {}
1648
+ currentBlockKeys,
1649
+ persistedState
1650
+ }) => ({
1651
+ ...restoreTimelineAnchorState({ messageId, currentBlockKeys, persistedState }),
1652
+ previousBlockKeys: persistedState?.previousBlockKeys ?? []
1560
1653
  });
1561
1654
  var timelineAnchorReducer = (state, action) => {
1562
1655
  switch (action.type) {
@@ -1564,7 +1657,7 @@ var timelineAnchorReducer = (state, action) => {
1564
1657
  if (state.messageId === action.messageId) {
1565
1658
  return state;
1566
1659
  }
1567
- return createTimelineAnchorState(action);
1660
+ return restoreTimelineAnchorState(action);
1568
1661
  case "sync-anchors": {
1569
1662
  const previousBlockKeys = new Set(state.previousBlockKeys);
1570
1663
  const nextAnchors = action.currentBlockKeys.reduce(
@@ -1633,10 +1726,15 @@ var useTimelineBlockAnchors = ({
1633
1726
  message,
1634
1727
  messageRenderOrder
1635
1728
  }) => {
1729
+ const { store } = useChatContext();
1636
1730
  const currentTimelineBlockKeys = (0, import_react6.useMemo)(
1637
1731
  () => blocks.map((block, index3) => getTimelineBlockKey(block, index3)).filter((blockKey) => Boolean(blockKey)),
1638
1732
  [blocks]
1639
1733
  );
1734
+ const persistedState = (0, import_react6.useMemo)(
1735
+ () => store.getState().timelineAnchorStateBySession[message.sessionId]?.[message.id],
1736
+ [message.id, message.sessionId, store]
1737
+ );
1640
1738
  const timelineTextStreamLength = (0, import_react6.useMemo)(
1641
1739
  () => getTimelineDisplayUnitCount(getTimelineTextStream(message.content, blocks)),
1642
1740
  [blocks, message.content]
@@ -1645,7 +1743,8 @@ var useTimelineBlockAnchors = ({
1645
1743
  timelineAnchorReducer,
1646
1744
  {
1647
1745
  messageId: message.id,
1648
- currentBlockKeys: currentTimelineBlockKeys
1746
+ currentBlockKeys: currentTimelineBlockKeys,
1747
+ persistedState
1649
1748
  },
1650
1749
  createInitialTimelineAnchorState
1651
1750
  );
@@ -1680,9 +1779,10 @@ var useTimelineBlockAnchors = ({
1680
1779
  dispatch({
1681
1780
  type: "reset-message",
1682
1781
  messageId: message.id,
1683
- currentBlockKeys: currentTimelineBlockKeys
1782
+ currentBlockKeys: currentTimelineBlockKeys,
1783
+ persistedState
1684
1784
  });
1685
- }, [currentTimelineBlockKeys, message.id]);
1785
+ }, [currentTimelineBlockKeys, message.id, persistedState]);
1686
1786
  (0, import_react6.useEffect)(() => {
1687
1787
  if (messageRenderOrder !== "timeline" || !isAssistantStreaming) {
1688
1788
  return;
@@ -1709,6 +1809,24 @@ var useTimelineBlockAnchors = ({
1709
1809
  effectiveTimelineBlockAnchors,
1710
1810
  messageRenderOrder
1711
1811
  ]);
1812
+ (0, import_react6.useEffect)(() => {
1813
+ if (messageRenderOrder !== "timeline") {
1814
+ return;
1815
+ }
1816
+ store.getState().setTimelineAnchorState(message.sessionId, message.id, {
1817
+ previousBlockKeys: state.previousBlockKeys,
1818
+ timelineBlockAnchors: state.timelineBlockAnchors,
1819
+ visibleTimelineBlockKeys: state.visibleTimelineBlockKeys
1820
+ });
1821
+ }, [
1822
+ message.id,
1823
+ message.sessionId,
1824
+ messageRenderOrder,
1825
+ state.previousBlockKeys,
1826
+ state.timelineBlockAnchors,
1827
+ state.visibleTimelineBlockKeys,
1828
+ store
1829
+ ]);
1712
1830
  return {
1713
1831
  timelineBlockAnchors: messageRenderOrder === "timeline" ? effectiveTimelineBlockAnchors : {},
1714
1832
  visibleTimelineBlockKeys: messageRenderOrder === "timeline" ? state.visibleTimelineBlockKeys : {}
@@ -3018,10 +3136,10 @@ var areExecutionProposalsEqual = (previousProposal, nextProposal) => previousPro
3018
3136
  (warning, index3) => warning === nextProposal.warnings?.[index3]
3019
3137
  );
3020
3138
  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]);
3021
- var areStringArraysEqual = (previousValues, nextValues) => previousValues.length === nextValues.length && previousValues.every((value, index3) => value === nextValues[index3]);
3139
+ var areStringArraysEqual2 = (previousValues, nextValues) => previousValues.length === nextValues.length && previousValues.every((value, index3) => value === nextValues[index3]);
3022
3140
  var areQuestionAnswersEqual = (previousAnswer, nextAnswer) => {
3023
3141
  if (Array.isArray(previousAnswer) || Array.isArray(nextAnswer)) {
3024
- return Array.isArray(previousAnswer) && Array.isArray(nextAnswer) && areStringArraysEqual(previousAnswer, nextAnswer);
3142
+ return Array.isArray(previousAnswer) && Array.isArray(nextAnswer) && areStringArraysEqual2(previousAnswer, nextAnswer);
3025
3143
  }
3026
3144
  return previousAnswer === nextAnswer;
3027
3145
  };
@@ -3108,7 +3226,7 @@ var areMessageBlocksEqual = (previousBlocks, nextBlocks) => {
3108
3226
  }
3109
3227
  });
3110
3228
  };
3111
- 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;
3229
+ 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;
3112
3230
  var ChatMessageItemView = ({
3113
3231
  message,
3114
3232
  mode = "agent",
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,8 @@ var createChatStore = (initialState) => createStore((set, get) => ({
202
214
  sessionMessageLoadStatusBySession: {},
203
215
  sessionMessageLoadErrorBySession: {},
204
216
  historyMessagePaginationBySession: {},
217
+ timelineAnchorStateBySession: {},
218
+ newlyCreatedSessionIds: [],
205
219
  // ---- Session management ------------------------------------------------
206
220
  createSession: (session) => {
207
221
  const state = get();
@@ -220,6 +234,9 @@ var createChatStore = (initialState) => createStore((set, get) => ({
220
234
  const nextHistoryMessagePaginationBySession = {
221
235
  ...state.historyMessagePaginationBySession
222
236
  };
237
+ const nextTimelineAnchorStateBySession = {
238
+ ...state.timelineAnchorStateBySession
239
+ };
223
240
  const sid = session.sessionId;
224
241
  if (nextMessagesBySession[sid] === void 0)
225
242
  nextMessagesBySession[sid] = [];
@@ -247,7 +264,8 @@ var createChatStore = (initialState) => createStore((set, get) => ({
247
264
  isStoppingBySession: nextIsStoppingBySession,
248
265
  sessionMessageLoadStatusBySession: nextSessionMessageLoadStatusBySession,
249
266
  sessionMessageLoadErrorBySession: nextSessionMessageLoadErrorBySession,
250
- historyMessagePaginationBySession: nextHistoryMessagePaginationBySession
267
+ historyMessagePaginationBySession: nextHistoryMessagePaginationBySession,
268
+ timelineAnchorStateBySession: nextTimelineAnchorStateBySession
251
269
  });
252
270
  },
253
271
  startNewChat: () => {
@@ -317,11 +335,19 @@ var createChatStore = (initialState) => createStore((set, get) => ({
317
335
  const nextHistoryMessagePaginationBySession = {
318
336
  ...state.historyMessagePaginationBySession
319
337
  };
338
+ const nextTimelineAnchorStateBySession = {
339
+ ...state.timelineAnchorStateBySession
340
+ };
320
341
  if (previousSessionId in nextHistoryMessagePaginationBySession) {
321
342
  nextHistoryMessagePaginationBySession[nextSessionId] = nextHistoryMessagePaginationBySession[previousSessionId] ?? createHistoryMessagePaginationState();
322
343
  delete nextHistoryMessagePaginationBySession[previousSessionId];
323
344
  }
345
+ if (previousSessionId in nextTimelineAnchorStateBySession) {
346
+ nextTimelineAnchorStateBySession[nextSessionId] = nextTimelineAnchorStateBySession[previousSessionId] ?? {};
347
+ delete nextTimelineAnchorStateBySession[previousSessionId];
348
+ }
324
349
  const nextActiveSessionId = state.activeSessionId === previousSessionId ? nextSessionId : state.activeSessionId;
350
+ const nextNewlyCreatedSessionIds = state.newlyCreatedSessionIds.includes(nextSessionId) ? state.newlyCreatedSessionIds : [...state.newlyCreatedSessionIds.filter((id) => id !== previousSessionId), nextSessionId];
325
351
  set({
326
352
  sessions: nextSessions,
327
353
  messagesBySession: nextMessagesBySession,
@@ -332,7 +358,9 @@ var createChatStore = (initialState) => createStore((set, get) => ({
332
358
  sessionMessageLoadStatusBySession: nextSessionMessageLoadStatusBySession,
333
359
  sessionMessageLoadErrorBySession: nextSessionMessageLoadErrorBySession,
334
360
  historyMessagePaginationBySession: nextHistoryMessagePaginationBySession,
335
- activeSessionId: nextActiveSessionId
361
+ timelineAnchorStateBySession: nextTimelineAnchorStateBySession,
362
+ activeSessionId: nextActiveSessionId,
363
+ newlyCreatedSessionIds: nextNewlyCreatedSessionIds
336
364
  });
337
365
  },
338
366
  setPreferredMode: (mode) => {
@@ -349,15 +377,28 @@ var createChatStore = (initialState) => createStore((set, get) => ({
349
377
  const hasActiveInIncoming = sessions.some(
350
378
  (session) => session.sessionId === state.activeSessionId
351
379
  );
352
- const keepActiveSession = activePersistedSession && !hasActiveInIncoming ? [activePersistedSession] : [];
380
+ let prependActiveSession = [];
381
+ let appendActiveSession = [];
382
+ if (activePersistedSession && !hasActiveInIncoming) {
383
+ const isNewlyCreated = state.newlyCreatedSessionIds.includes(
384
+ activePersistedSession.sessionId
385
+ );
386
+ if (isNewlyCreated) {
387
+ prependActiveSession = [activePersistedSession];
388
+ } else {
389
+ appendActiveSession = [activePersistedSession];
390
+ }
391
+ }
353
392
  const excludeIds = /* @__PURE__ */ new Set([
354
393
  ...draftSessions.map((s) => s.sessionId),
355
- ...keepActiveSession.map((s) => s.sessionId)
394
+ ...prependActiveSession.map((s) => s.sessionId),
395
+ ...appendActiveSession.map((s) => s.sessionId)
356
396
  ]);
357
397
  const nextSessions = [
358
398
  ...draftSessions,
399
+ ...prependActiveSession,
359
400
  ...sessions.filter((session) => !excludeIds.has(session.sessionId)),
360
- ...keepActiveSession
401
+ ...appendActiveSession
361
402
  ];
362
403
  const nextMessagesBySession = { ...state.messagesBySession };
363
404
  const nextErrorBySession = { ...state.errorBySession };
@@ -372,6 +413,9 @@ var createChatStore = (initialState) => createStore((set, get) => ({
372
413
  const nextHistoryMessagePaginationBySession = {
373
414
  ...state.historyMessagePaginationBySession
374
415
  };
416
+ const nextTimelineAnchorStateBySession = {
417
+ ...state.timelineAnchorStateBySession
418
+ };
375
419
  nextSessions.forEach((session) => {
376
420
  const sid = session.sessionId;
377
421
  if (nextErrorBySession[sid] === void 0)
@@ -390,6 +434,10 @@ var createChatStore = (initialState) => createStore((set, get) => ({
390
434
  nextHistoryMessagePaginationBySession[sid] = createHistoryMessagePaginationState();
391
435
  }
392
436
  });
437
+ const incomingSessionIds = new Set(sessions.map((s) => s.sessionId));
438
+ const nextNewlyCreatedSessionIds = state.newlyCreatedSessionIds.filter(
439
+ (id) => !incomingSessionIds.has(id)
440
+ );
393
441
  set({
394
442
  sessions: nextSessions,
395
443
  messagesBySession: nextMessagesBySession,
@@ -398,7 +446,9 @@ var createChatStore = (initialState) => createStore((set, get) => ({
398
446
  isStoppingBySession: nextIsStoppingBySession,
399
447
  sessionMessageLoadStatusBySession: nextSessionMessageLoadStatusBySession,
400
448
  sessionMessageLoadErrorBySession: nextSessionMessageLoadErrorBySession,
401
- historyMessagePaginationBySession: nextHistoryMessagePaginationBySession
449
+ historyMessagePaginationBySession: nextHistoryMessagePaginationBySession,
450
+ timelineAnchorStateBySession: nextTimelineAnchorStateBySession,
451
+ newlyCreatedSessionIds: nextNewlyCreatedSessionIds
402
452
  });
403
453
  },
404
454
  // ---- Message operations ------------------------------------------------
@@ -513,6 +563,23 @@ var createChatStore = (initialState) => createStore((set, get) => ({
513
563
  }
514
564
  });
515
565
  },
566
+ setTimelineAnchorState: (sessionId, messageId, snapshot) => {
567
+ const state = get();
568
+ const currentSessionSnapshots = state.timelineAnchorStateBySession[sessionId] ?? {};
569
+ const currentSnapshot = currentSessionSnapshots[messageId];
570
+ if (isSameTimelineAnchorSnapshot(currentSnapshot, snapshot)) {
571
+ return;
572
+ }
573
+ set({
574
+ timelineAnchorStateBySession: {
575
+ ...state.timelineAnchorStateBySession,
576
+ [sessionId]: {
577
+ ...currentSessionSnapshots,
578
+ [messageId]: snapshot
579
+ }
580
+ }
581
+ });
582
+ },
516
583
  startStreamingMessage: (sessionId, message) => {
517
584
  const state = get();
518
585
  set({
@@ -1503,13 +1570,39 @@ var createTimelineAnchorState = ({
1503
1570
  timelineBlockAnchors: {},
1504
1571
  visibleTimelineBlockKeys: {}
1505
1572
  });
1573
+ var restoreTimelineAnchorState = ({
1574
+ messageId,
1575
+ currentBlockKeys,
1576
+ persistedState
1577
+ }) => {
1578
+ if (!persistedState) {
1579
+ return createTimelineAnchorState({ messageId, currentBlockKeys });
1580
+ }
1581
+ const currentBlockKeySet = new Set(currentBlockKeys);
1582
+ return {
1583
+ messageId,
1584
+ previousBlockKeys: persistedState.previousBlockKeys.filter(
1585
+ (blockKey) => currentBlockKeySet.has(blockKey)
1586
+ ),
1587
+ timelineBlockAnchors: Object.fromEntries(
1588
+ Object.entries(persistedState.timelineBlockAnchors).filter(
1589
+ ([blockKey]) => currentBlockKeySet.has(blockKey)
1590
+ )
1591
+ ),
1592
+ visibleTimelineBlockKeys: Object.fromEntries(
1593
+ Object.entries(persistedState.visibleTimelineBlockKeys).filter(
1594
+ ([blockKey]) => currentBlockKeySet.has(blockKey)
1595
+ )
1596
+ )
1597
+ };
1598
+ };
1506
1599
  var createInitialTimelineAnchorState = ({
1507
- messageId
1508
- }) => ({
1509
1600
  messageId,
1510
- previousBlockKeys: [],
1511
- timelineBlockAnchors: {},
1512
- visibleTimelineBlockKeys: {}
1601
+ currentBlockKeys,
1602
+ persistedState
1603
+ }) => ({
1604
+ ...restoreTimelineAnchorState({ messageId, currentBlockKeys, persistedState }),
1605
+ previousBlockKeys: persistedState?.previousBlockKeys ?? []
1513
1606
  });
1514
1607
  var timelineAnchorReducer = (state, action) => {
1515
1608
  switch (action.type) {
@@ -1517,7 +1610,7 @@ var timelineAnchorReducer = (state, action) => {
1517
1610
  if (state.messageId === action.messageId) {
1518
1611
  return state;
1519
1612
  }
1520
- return createTimelineAnchorState(action);
1613
+ return restoreTimelineAnchorState(action);
1521
1614
  case "sync-anchors": {
1522
1615
  const previousBlockKeys = new Set(state.previousBlockKeys);
1523
1616
  const nextAnchors = action.currentBlockKeys.reduce(
@@ -1586,10 +1679,15 @@ var useTimelineBlockAnchors = ({
1586
1679
  message,
1587
1680
  messageRenderOrder
1588
1681
  }) => {
1682
+ const { store } = useChatContext();
1589
1683
  const currentTimelineBlockKeys = useMemo3(
1590
1684
  () => blocks.map((block, index3) => getTimelineBlockKey(block, index3)).filter((blockKey) => Boolean(blockKey)),
1591
1685
  [blocks]
1592
1686
  );
1687
+ const persistedState = useMemo3(
1688
+ () => store.getState().timelineAnchorStateBySession[message.sessionId]?.[message.id],
1689
+ [message.id, message.sessionId, store]
1690
+ );
1593
1691
  const timelineTextStreamLength = useMemo3(
1594
1692
  () => getTimelineDisplayUnitCount(getTimelineTextStream(message.content, blocks)),
1595
1693
  [blocks, message.content]
@@ -1598,7 +1696,8 @@ var useTimelineBlockAnchors = ({
1598
1696
  timelineAnchorReducer,
1599
1697
  {
1600
1698
  messageId: message.id,
1601
- currentBlockKeys: currentTimelineBlockKeys
1699
+ currentBlockKeys: currentTimelineBlockKeys,
1700
+ persistedState
1602
1701
  },
1603
1702
  createInitialTimelineAnchorState
1604
1703
  );
@@ -1633,9 +1732,10 @@ var useTimelineBlockAnchors = ({
1633
1732
  dispatch({
1634
1733
  type: "reset-message",
1635
1734
  messageId: message.id,
1636
- currentBlockKeys: currentTimelineBlockKeys
1735
+ currentBlockKeys: currentTimelineBlockKeys,
1736
+ persistedState
1637
1737
  });
1638
- }, [currentTimelineBlockKeys, message.id]);
1738
+ }, [currentTimelineBlockKeys, message.id, persistedState]);
1639
1739
  useEffect3(() => {
1640
1740
  if (messageRenderOrder !== "timeline" || !isAssistantStreaming) {
1641
1741
  return;
@@ -1662,6 +1762,24 @@ var useTimelineBlockAnchors = ({
1662
1762
  effectiveTimelineBlockAnchors,
1663
1763
  messageRenderOrder
1664
1764
  ]);
1765
+ useEffect3(() => {
1766
+ if (messageRenderOrder !== "timeline") {
1767
+ return;
1768
+ }
1769
+ store.getState().setTimelineAnchorState(message.sessionId, message.id, {
1770
+ previousBlockKeys: state.previousBlockKeys,
1771
+ timelineBlockAnchors: state.timelineBlockAnchors,
1772
+ visibleTimelineBlockKeys: state.visibleTimelineBlockKeys
1773
+ });
1774
+ }, [
1775
+ message.id,
1776
+ message.sessionId,
1777
+ messageRenderOrder,
1778
+ state.previousBlockKeys,
1779
+ state.timelineBlockAnchors,
1780
+ state.visibleTimelineBlockKeys,
1781
+ store
1782
+ ]);
1665
1783
  return {
1666
1784
  timelineBlockAnchors: messageRenderOrder === "timeline" ? effectiveTimelineBlockAnchors : {},
1667
1785
  visibleTimelineBlockKeys: messageRenderOrder === "timeline" ? state.visibleTimelineBlockKeys : {}
@@ -2975,10 +3093,10 @@ var areExecutionProposalsEqual = (previousProposal, nextProposal) => previousPro
2975
3093
  (warning, index3) => warning === nextProposal.warnings?.[index3]
2976
3094
  );
2977
3095
  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]);
2978
- var areStringArraysEqual = (previousValues, nextValues) => previousValues.length === nextValues.length && previousValues.every((value, index3) => value === nextValues[index3]);
3096
+ var areStringArraysEqual2 = (previousValues, nextValues) => previousValues.length === nextValues.length && previousValues.every((value, index3) => value === nextValues[index3]);
2979
3097
  var areQuestionAnswersEqual = (previousAnswer, nextAnswer) => {
2980
3098
  if (Array.isArray(previousAnswer) || Array.isArray(nextAnswer)) {
2981
- return Array.isArray(previousAnswer) && Array.isArray(nextAnswer) && areStringArraysEqual(previousAnswer, nextAnswer);
3099
+ return Array.isArray(previousAnswer) && Array.isArray(nextAnswer) && areStringArraysEqual2(previousAnswer, nextAnswer);
2982
3100
  }
2983
3101
  return previousAnswer === nextAnswer;
2984
3102
  };
@@ -3065,7 +3183,7 @@ var areMessageBlocksEqual = (previousBlocks, nextBlocks) => {
3065
3183
  }
3066
3184
  });
3067
3185
  };
3068
- 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;
3186
+ 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;
3069
3187
  var ChatMessageItemView = ({
3070
3188
  message,
3071
3189
  mode = "agent",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xinghunm/ai-chat",
3
- "version": "1.4.2",
3
+ "version": "1.4.4",
4
4
  "description": "AI chat React component library",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",