@xinghunm/ai-chat 1.3.1 → 1.3.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.js CHANGED
@@ -852,6 +852,21 @@ var AiChatProvider = (props) => {
852
852
  defaultAuthToken,
853
853
  defaultTransformStreamPacket
854
854
  ]);
855
+ const latestTransportRef = (0, import_react2.useRef)(transport);
856
+ (0, import_react2.useLayoutEffect)(() => {
857
+ latestTransportRef.current = transport;
858
+ }, [transport]);
859
+ (0, import_react2.useEffect)(() => {
860
+ return () => {
861
+ const { isStreamingBySession } = store.getState();
862
+ const streamingIds = Object.entries(isStreamingBySession).filter(([, streaming]) => streaming).map(([id]) => id);
863
+ for (const sessionId of streamingIds) {
864
+ const terminateId = isDraftChatSessionId(sessionId) ? void 0 : sessionId;
865
+ void latestTransportRef.current.terminateStream(terminateId).catch(() => {
866
+ });
867
+ }
868
+ };
869
+ }, [store]);
855
870
  const contextValue = (0, import_react2.useMemo)(
856
871
  () => ({
857
872
  store,
@@ -1323,6 +1338,14 @@ var createTimelineAnchorState = ({
1323
1338
  timelineBlockAnchors: {},
1324
1339
  visibleTimelineBlockKeys: {}
1325
1340
  });
1341
+ var createInitialTimelineAnchorState = ({
1342
+ messageId
1343
+ }) => ({
1344
+ messageId,
1345
+ previousBlockKeys: [],
1346
+ timelineBlockAnchors: {},
1347
+ visibleTimelineBlockKeys: {}
1348
+ });
1326
1349
  var timelineAnchorReducer = (state, action) => {
1327
1350
  switch (action.type) {
1328
1351
  case "reset-message":
@@ -1412,7 +1435,7 @@ var useTimelineBlockAnchors = ({
1412
1435
  messageId: message.id,
1413
1436
  currentBlockKeys: currentTimelineBlockKeys
1414
1437
  },
1415
- createTimelineAnchorState
1438
+ createInitialTimelineAnchorState
1416
1439
  );
1417
1440
  const effectiveTimelineBlockAnchors = (0, import_react6.useMemo)(() => {
1418
1441
  if (messageRenderOrder !== "timeline" || !isAssistantStreaming) {
@@ -3609,7 +3632,6 @@ var ChatThreadView = ({
3609
3632
  [historyMessages, streamingMessage]
3610
3633
  );
3611
3634
  const latestTurn = conversationTurns[conversationTurns.length - 1];
3612
- const previousTurns = conversationTurns.slice(0, -1);
3613
3635
  const latestUserMessageId = latestTurn?.userMessage?.id;
3614
3636
  const latestHistoryMessage = historyMessages[historyMessages.length - 1];
3615
3637
  const latestTurnRef = (0, import_react11.useRef)(null);
@@ -3798,54 +3820,42 @@ var ChatThreadView = ({
3798
3820
  }, [latestTurn, scrollToBottom]);
3799
3821
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(ThreadViewport, { children: [
3800
3822
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Container, { ref: containerRef, "data-testid": "chat-thread", onScroll: handleContainerScroll, children: [
3801
- previousTurns.map((turn) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(ConversationTurn, { "data-testid": "chat-thread-turn", children: [
3802
- turn.userMessage ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MessageSlot, { children: renderChatMessage({
3803
- message: turn.userMessage,
3804
- mode: activeSessionMode,
3805
- onConfirmationSubmit,
3806
- onQuestionnaireSubmit,
3807
- renderMessageBlock
3808
- }) }) : null,
3809
- turn.responseMessages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MessageSlot, { children: renderChatMessage({
3810
- message,
3811
- mode: activeSessionMode,
3812
- onConfirmationSubmit,
3813
- onQuestionnaireSubmit,
3814
- renderMessageBlock
3815
- }) }, message.id))
3816
- ] }, turn.id)),
3817
- latestTurn ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3818
- ConversationTurn,
3819
- {
3820
- ref: latestTurnRef,
3821
- "data-testid": "chat-thread-latest-turn",
3822
- style: latestTurnMinHeight > 0 ? { minHeight: `${latestTurnMinHeight}px` } : void 0,
3823
- children: [
3824
- latestTurn.userMessage ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3825
- MessageSlot,
3826
- {
3827
- "data-testid": "chat-latest-user-anchor",
3828
- style: { scrollMarginTop: `${CHAT_THREAD_SCROLL_TOP_GAP}px` },
3829
- children: renderChatMessage({
3830
- message: latestTurn.userMessage,
3831
- mode: activeSessionMode,
3832
- onConfirmationSubmit,
3833
- onQuestionnaireSubmit,
3834
- renderMessageBlock
3835
- })
3836
- }
3837
- ) : null,
3838
- latestTurn.responseMessages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MessageSlot, { children: renderChatMessage({
3839
- message,
3840
- mode: activeSessionMode,
3841
- onConfirmationSubmit,
3842
- onQuestionnaireSubmit,
3843
- renderMessageBlock
3844
- }) }, message.id)),
3845
- error2 ? renderErrorState({ error: error2, onRetry, retryButtonLabel }) : null
3846
- ]
3847
- }
3848
- ) : null,
3823
+ conversationTurns.map((turn, turnIndex) => {
3824
+ const isLatestTurn = turnIndex === conversationTurns.length - 1;
3825
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3826
+ ConversationTurn,
3827
+ {
3828
+ ref: isLatestTurn ? latestTurnRef : null,
3829
+ "data-testid": isLatestTurn ? "chat-thread-latest-turn" : "chat-thread-turn",
3830
+ style: isLatestTurn && latestTurnMinHeight > 0 ? { minHeight: `${latestTurnMinHeight}px` } : void 0,
3831
+ children: [
3832
+ turn.userMessage ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3833
+ MessageSlot,
3834
+ {
3835
+ "data-testid": isLatestTurn ? "chat-latest-user-anchor" : void 0,
3836
+ style: isLatestTurn ? { scrollMarginTop: `${CHAT_THREAD_SCROLL_TOP_GAP}px` } : void 0,
3837
+ children: renderChatMessage({
3838
+ message: turn.userMessage,
3839
+ mode: activeSessionMode,
3840
+ onConfirmationSubmit,
3841
+ onQuestionnaireSubmit,
3842
+ renderMessageBlock
3843
+ })
3844
+ }
3845
+ ) : null,
3846
+ turn.responseMessages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MessageSlot, { children: renderChatMessage({
3847
+ message,
3848
+ mode: activeSessionMode,
3849
+ onConfirmationSubmit,
3850
+ onQuestionnaireSubmit,
3851
+ renderMessageBlock
3852
+ }) }, message.id)),
3853
+ isLatestTurn && error2 ? renderErrorState({ error: error2, onRetry, retryButtonLabel }) : null
3854
+ ]
3855
+ },
3856
+ turn.id
3857
+ );
3858
+ }),
3849
3859
  !latestTurn && error2 ? renderErrorState({ error: error2, onRetry, retryButtonLabel }) : null
3850
3860
  ] }),
3851
3861
  isDetached ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ScrollToLatestOverlay, { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
@@ -7224,6 +7234,9 @@ var useChatComposer = () => {
7224
7234
  }
7225
7235
  if (isDraftChatSessionId(sessionId)) {
7226
7236
  finalizeStop(sessionId);
7237
+ void transport.terminateStream(void 0).catch((err) => {
7238
+ console.error("Failed to terminate draft chat session", err);
7239
+ });
7227
7240
  return;
7228
7241
  }
7229
7242
  requestStopStreaming(sessionId);
package/dist/index.mjs CHANGED
@@ -1,10 +1,10 @@
1
1
  // src/components/ai-chat/index.tsx
2
- import { useEffect as useEffect9 } from "react";
2
+ import { useEffect as useEffect10 } from "react";
3
3
  import styled17 from "@emotion/styled";
4
4
  import { ConfigProvider } from "@xinghunm/compass-ui";
5
5
 
6
6
  // src/components/ai-chat-provider/index.tsx
7
- import { useRef, useMemo, useState } from "react";
7
+ import { useRef, useMemo, useState, useEffect, useLayoutEffect } from "react";
8
8
  import axios2 from "axios";
9
9
 
10
10
  // src/context/chat-context.ts
@@ -805,6 +805,21 @@ var AiChatProvider = (props) => {
805
805
  defaultAuthToken,
806
806
  defaultTransformStreamPacket
807
807
  ]);
808
+ const latestTransportRef = useRef(transport);
809
+ useLayoutEffect(() => {
810
+ latestTransportRef.current = transport;
811
+ }, [transport]);
812
+ useEffect(() => {
813
+ return () => {
814
+ const { isStreamingBySession } = store.getState();
815
+ const streamingIds = Object.entries(isStreamingBySession).filter(([, streaming]) => streaming).map(([id]) => id);
816
+ for (const sessionId of streamingIds) {
817
+ const terminateId = isDraftChatSessionId(sessionId) ? void 0 : sessionId;
818
+ void latestTransportRef.current.terminateStream(terminateId).catch(() => {
819
+ });
820
+ }
821
+ };
822
+ }, [store]);
808
823
  const contextValue = useMemo(
809
824
  () => ({
810
825
  store,
@@ -845,7 +860,7 @@ var AiChatProvider = (props) => {
845
860
  };
846
861
 
847
862
  // src/components/chat-thread/index.tsx
848
- import { useCallback as useCallback3, useLayoutEffect as useLayoutEffect2, useMemo as useMemo4, useRef as useRef5, useState as useState4 } from "react";
863
+ import { useCallback as useCallback3, useLayoutEffect as useLayoutEffect3, useMemo as useMemo4, useRef as useRef5, useState as useState4 } from "react";
849
864
  import styled9 from "@emotion/styled";
850
865
 
851
866
  // src/context/use-chat-context.ts
@@ -866,7 +881,7 @@ var useChatStore = (selector) => {
866
881
  var CHAT_THREAD_SCROLL_TOP_GAP = 16;
867
882
 
868
883
  // src/components/chat-thread/components/chat-message-item.tsx
869
- import { Fragment, memo, useCallback as useCallback2, useLayoutEffect, useState as useState3 } from "react";
884
+ import { Fragment, memo, useCallback as useCallback2, useLayoutEffect as useLayoutEffect2, useState as useState3 } from "react";
870
885
  import styled7 from "@emotion/styled";
871
886
  import { keyframes } from "@emotion/react";
872
887
  import ReactMarkdown from "react-markdown";
@@ -875,7 +890,7 @@ import remarkMath from "remark-math";
875
890
  import rehypeKatex from "rehype-katex";
876
891
 
877
892
  // src/components/chat-thread/hooks/use-chat-message-reveal.ts
878
- import { useCallback, useEffect, useMemo as useMemo2, useReducer, useRef as useRef2 } from "react";
893
+ import { useCallback, useEffect as useEffect2, useMemo as useMemo2, useReducer, useRef as useRef2 } from "react";
879
894
 
880
895
  // src/components/chat-thread/lib/message-reveal.ts
881
896
  var STREAM_REVEAL_TICK_MS = 36;
@@ -1010,7 +1025,7 @@ var useChatMessageReveal = (message) => {
1010
1025
  },
1011
1026
  [isAssistantStreaming, message.role]
1012
1027
  );
1013
- useEffect(() => {
1028
+ useEffect2(() => {
1014
1029
  if (previousMessageIdRef.current === message.id) {
1015
1030
  return;
1016
1031
  }
@@ -1028,7 +1043,7 @@ var useChatMessageReveal = (message) => {
1028
1043
  targetUnitCount: targetUnits.length
1029
1044
  });
1030
1045
  }, [isAssistantStreaming, message.id, targetUnits.length]);
1031
- useEffect(() => {
1046
+ useEffect2(() => {
1032
1047
  pendingTargetUnitCountRef.current = targetUnits.length;
1033
1048
  if (message.role !== "assistant" || !isAssistantStreaming) {
1034
1049
  if (inputBatchTimeoutRef.current !== null) {
@@ -1064,7 +1079,7 @@ var useChatMessageReveal = (message) => {
1064
1079
  [displayedUnitCount, targetUnits]
1065
1080
  );
1066
1081
  const contentBlocks = useMemo2(() => splitMarkdownBlocks(displayedContent), [displayedContent]);
1067
- useEffect(() => {
1082
+ useEffect2(() => {
1068
1083
  const hasNewDisplayedBlock = message.role === "assistant" && contentBlocks.length > 1 && contentBlocks.length > lastDisplayedBlockCountRef.current;
1069
1084
  lastDisplayedBlockCountRef.current = contentBlocks.length;
1070
1085
  if (!hasNewDisplayedBlock) {
@@ -1078,7 +1093,7 @@ var useChatMessageReveal = (message) => {
1078
1093
  window.clearTimeout(timer);
1079
1094
  };
1080
1095
  }, [contentBlocks.length, message.role]);
1081
- useEffect(() => {
1096
+ useEffect2(() => {
1082
1097
  const shouldAnimateReveal = message.role === "assistant" && displayedUnitCount < batchedTargetUnitCount && (isAssistantStreaming || displayedUnitCount > 0);
1083
1098
  if (!shouldAnimateReveal) {
1084
1099
  if (displayedUnitCount !== batchedTargetUnitCount) {
@@ -1115,7 +1130,7 @@ var useChatMessageReveal = (message) => {
1115
1130
  };
1116
1131
 
1117
1132
  // src/components/chat-thread/hooks/use-timeline-block-anchors.ts
1118
- import { useEffect as useEffect2, useMemo as useMemo3, useReducer as useReducer2 } from "react";
1133
+ import { useEffect as useEffect3, useMemo as useMemo3, useReducer as useReducer2 } from "react";
1119
1134
 
1120
1135
  // src/components/chat-thread/lib/chat-message-timeline.ts
1121
1136
  var stringifyTimelineKeyPart = (value) => {
@@ -1276,6 +1291,14 @@ var createTimelineAnchorState = ({
1276
1291
  timelineBlockAnchors: {},
1277
1292
  visibleTimelineBlockKeys: {}
1278
1293
  });
1294
+ var createInitialTimelineAnchorState = ({
1295
+ messageId
1296
+ }) => ({
1297
+ messageId,
1298
+ previousBlockKeys: [],
1299
+ timelineBlockAnchors: {},
1300
+ visibleTimelineBlockKeys: {}
1301
+ });
1279
1302
  var timelineAnchorReducer = (state, action) => {
1280
1303
  switch (action.type) {
1281
1304
  case "reset-message":
@@ -1365,7 +1388,7 @@ var useTimelineBlockAnchors = ({
1365
1388
  messageId: message.id,
1366
1389
  currentBlockKeys: currentTimelineBlockKeys
1367
1390
  },
1368
- createTimelineAnchorState
1391
+ createInitialTimelineAnchorState
1369
1392
  );
1370
1393
  const effectiveTimelineBlockAnchors = useMemo3(() => {
1371
1394
  if (messageRenderOrder !== "timeline" || !isAssistantStreaming) {
@@ -1394,14 +1417,14 @@ var useTimelineBlockAnchors = ({
1394
1417
  state.timelineBlockAnchors,
1395
1418
  timelineTextStreamLength
1396
1419
  ]);
1397
- useEffect2(() => {
1420
+ useEffect3(() => {
1398
1421
  dispatch({
1399
1422
  type: "reset-message",
1400
1423
  messageId: message.id,
1401
1424
  currentBlockKeys: currentTimelineBlockKeys
1402
1425
  });
1403
1426
  }, [currentTimelineBlockKeys, message.id]);
1404
- useEffect2(() => {
1427
+ useEffect3(() => {
1405
1428
  if (messageRenderOrder !== "timeline" || !isAssistantStreaming) {
1406
1429
  return;
1407
1430
  }
@@ -1411,7 +1434,7 @@ var useTimelineBlockAnchors = ({
1411
1434
  timelineTextStreamLength
1412
1435
  });
1413
1436
  }, [currentTimelineBlockKeys, isAssistantStreaming, messageRenderOrder, timelineTextStreamLength]);
1414
- useEffect2(() => {
1437
+ useEffect3(() => {
1415
1438
  if (messageRenderOrder !== "timeline") {
1416
1439
  return;
1417
1440
  }
@@ -1636,7 +1659,7 @@ var Value = styled3.span`
1636
1659
 
1637
1660
  // src/components/chat-thread/components/questionnaire-card.tsx
1638
1661
  import {
1639
- useEffect as useEffect3,
1662
+ useEffect as useEffect4,
1640
1663
  useRef as useRef3,
1641
1664
  useState as useState2
1642
1665
  } from "react";
@@ -2029,11 +2052,11 @@ var QuestionnaireCardInner = ({
2029
2052
  const visibleErrorMessage = questionnaire.statusMessage ?? errorMessage;
2030
2053
  const isInteractionLocked = !interactive || isSubmitting || isSubmitted || hasExternalFailureStatus;
2031
2054
  questionnaireRef.current = questionnaire;
2032
- useEffect3(() => {
2055
+ useEffect4(() => {
2033
2056
  setAnswers(createInitialAnswers(questionnaireRef.current));
2034
2057
  setOtherDrafts(createInitialOtherDrafts(questionnaireRef.current));
2035
2058
  }, [questionnaire.answers]);
2036
- useEffect3(() => {
2059
+ useEffect4(() => {
2037
2060
  if (!pendingFocusQuestionId || isInteractionLocked) {
2038
2061
  return;
2039
2062
  }
@@ -2557,7 +2580,7 @@ var Detail = styled5.li`
2557
2580
 
2558
2581
  // src/components/chat-thread/components/image-viewer.tsx
2559
2582
  import styled6 from "@emotion/styled";
2560
- import { useEffect as useEffect4, useRef as useRef4 } from "react";
2583
+ import { useEffect as useEffect5, useRef as useRef4 } from "react";
2561
2584
  import { jsx as jsx7 } from "@emotion/react/jsx-runtime";
2562
2585
  var Overlay = styled6.div`
2563
2586
  position: fixed;
@@ -2577,7 +2600,7 @@ var Img = styled6.img`
2577
2600
  `;
2578
2601
  var ImageViewer = ({ src, alt, onClose }) => {
2579
2602
  const overlayRef = useRef4(null);
2580
- useEffect4(() => {
2603
+ useEffect5(() => {
2581
2604
  const handleKey = (e) => {
2582
2605
  if (e.key === "Escape")
2583
2606
  onClose();
@@ -2585,7 +2608,7 @@ var ImageViewer = ({ src, alt, onClose }) => {
2585
2608
  document.addEventListener("keydown", handleKey);
2586
2609
  return () => document.removeEventListener("keydown", handleKey);
2587
2610
  }, [onClose]);
2588
- useEffect4(() => {
2611
+ useEffect5(() => {
2589
2612
  overlayRef.current?.focus();
2590
2613
  }, []);
2591
2614
  const stopPropagation = (e) => e.stopPropagation();
@@ -2671,7 +2694,7 @@ var useUserMessageCollapse = ({
2671
2694
  },
2672
2695
  [syncCollapseState]
2673
2696
  );
2674
- useLayoutEffect(() => {
2697
+ useLayoutEffect2(() => {
2675
2698
  if (!bodyStackElement) {
2676
2699
  return;
2677
2700
  }
@@ -2691,7 +2714,7 @@ var useUserMessageCollapse = ({
2691
2714
  settledContent,
2692
2715
  syncCollapseState
2693
2716
  ]);
2694
- useLayoutEffect(() => {
2717
+ useLayoutEffect2(() => {
2695
2718
  if (!bodyStackElement || !enabled || typeof ResizeObserver === "undefined") {
2696
2719
  return;
2697
2720
  }
@@ -3566,7 +3589,6 @@ var ChatThreadView = ({
3566
3589
  [historyMessages, streamingMessage]
3567
3590
  );
3568
3591
  const latestTurn = conversationTurns[conversationTurns.length - 1];
3569
- const previousTurns = conversationTurns.slice(0, -1);
3570
3592
  const latestUserMessageId = latestTurn?.userMessage?.id;
3571
3593
  const latestHistoryMessage = historyMessages[historyMessages.length - 1];
3572
3594
  const latestTurnRef = useRef5(null);
@@ -3628,7 +3650,7 @@ var ChatThreadView = ({
3628
3650
  setPendingNewMessageCount(0);
3629
3651
  }
3630
3652
  }, []);
3631
- useLayoutEffect2(() => {
3653
+ useLayoutEffect3(() => {
3632
3654
  const nextHistoryMessageId = latestHistoryMessage?.id;
3633
3655
  if (lastHistoryMessageIdRef.current === nextHistoryMessageId) {
3634
3656
  return;
@@ -3652,7 +3674,7 @@ var ChatThreadView = ({
3652
3674
  });
3653
3675
  }
3654
3676
  }, [latestHistoryMessage, markThreadPinned, scrollToBottom]);
3655
- useLayoutEffect2(() => {
3677
+ useLayoutEffect3(() => {
3656
3678
  const nextStreamingMessageId = streamingMessage?.id;
3657
3679
  if (lastStreamingMessageIdRef.current === nextStreamingMessageId) {
3658
3680
  return;
@@ -3667,7 +3689,7 @@ var ChatThreadView = ({
3667
3689
  });
3668
3690
  }
3669
3691
  }, [streamingMessage]);
3670
- useLayoutEffect2(() => {
3692
+ useLayoutEffect3(() => {
3671
3693
  if (reservedSpaceFrameRef.current !== null) {
3672
3694
  window.cancelAnimationFrame(reservedSpaceFrameRef.current);
3673
3695
  reservedSpaceFrameRef.current = null;
@@ -3697,7 +3719,7 @@ var ChatThreadView = ({
3697
3719
  }
3698
3720
  };
3699
3721
  }, [latestTurn, latestUserMessageId, error2, measureLatestTurnMinHeight, scrollToBottom]);
3700
- useLayoutEffect2(() => {
3722
+ useLayoutEffect3(() => {
3701
3723
  if (!latestTurn)
3702
3724
  return;
3703
3725
  const handleResize = () => {
@@ -3723,7 +3745,7 @@ var ChatThreadView = ({
3723
3745
  window.removeEventListener("resize", handleResize);
3724
3746
  };
3725
3747
  }, [latestTurn, latestUserMessageId, measureLatestTurnMinHeight, scrollToBottom]);
3726
- useLayoutEffect2(() => {
3748
+ useLayoutEffect3(() => {
3727
3749
  const latestTurnElement = latestTurnRef.current;
3728
3750
  if (!latestTurnElement || typeof ResizeObserver === "undefined") {
3729
3751
  return;
@@ -3736,7 +3758,7 @@ var ChatThreadView = ({
3736
3758
  observer.disconnect();
3737
3759
  };
3738
3760
  }, [latestTurn, scrollToBottom]);
3739
- useLayoutEffect2(() => {
3761
+ useLayoutEffect3(() => {
3740
3762
  const latestTurnElement = latestTurnRef.current;
3741
3763
  if (!latestTurnElement || typeof MutationObserver === "undefined") {
3742
3764
  return;
@@ -3755,54 +3777,42 @@ var ChatThreadView = ({
3755
3777
  }, [latestTurn, scrollToBottom]);
3756
3778
  return /* @__PURE__ */ jsxs7(ThreadViewport, { children: [
3757
3779
  /* @__PURE__ */ jsxs7(Container, { ref: containerRef, "data-testid": "chat-thread", onScroll: handleContainerScroll, children: [
3758
- previousTurns.map((turn) => /* @__PURE__ */ jsxs7(ConversationTurn, { "data-testid": "chat-thread-turn", children: [
3759
- turn.userMessage ? /* @__PURE__ */ jsx10(MessageSlot, { children: renderChatMessage({
3760
- message: turn.userMessage,
3761
- mode: activeSessionMode,
3762
- onConfirmationSubmit,
3763
- onQuestionnaireSubmit,
3764
- renderMessageBlock
3765
- }) }) : null,
3766
- turn.responseMessages.map((message) => /* @__PURE__ */ jsx10(MessageSlot, { children: renderChatMessage({
3767
- message,
3768
- mode: activeSessionMode,
3769
- onConfirmationSubmit,
3770
- onQuestionnaireSubmit,
3771
- renderMessageBlock
3772
- }) }, message.id))
3773
- ] }, turn.id)),
3774
- latestTurn ? /* @__PURE__ */ jsxs7(
3775
- ConversationTurn,
3776
- {
3777
- ref: latestTurnRef,
3778
- "data-testid": "chat-thread-latest-turn",
3779
- style: latestTurnMinHeight > 0 ? { minHeight: `${latestTurnMinHeight}px` } : void 0,
3780
- children: [
3781
- latestTurn.userMessage ? /* @__PURE__ */ jsx10(
3782
- MessageSlot,
3783
- {
3784
- "data-testid": "chat-latest-user-anchor",
3785
- style: { scrollMarginTop: `${CHAT_THREAD_SCROLL_TOP_GAP}px` },
3786
- children: renderChatMessage({
3787
- message: latestTurn.userMessage,
3788
- mode: activeSessionMode,
3789
- onConfirmationSubmit,
3790
- onQuestionnaireSubmit,
3791
- renderMessageBlock
3792
- })
3793
- }
3794
- ) : null,
3795
- latestTurn.responseMessages.map((message) => /* @__PURE__ */ jsx10(MessageSlot, { children: renderChatMessage({
3796
- message,
3797
- mode: activeSessionMode,
3798
- onConfirmationSubmit,
3799
- onQuestionnaireSubmit,
3800
- renderMessageBlock
3801
- }) }, message.id)),
3802
- error2 ? renderErrorState({ error: error2, onRetry, retryButtonLabel }) : null
3803
- ]
3804
- }
3805
- ) : null,
3780
+ conversationTurns.map((turn, turnIndex) => {
3781
+ const isLatestTurn = turnIndex === conversationTurns.length - 1;
3782
+ return /* @__PURE__ */ jsxs7(
3783
+ ConversationTurn,
3784
+ {
3785
+ ref: isLatestTurn ? latestTurnRef : null,
3786
+ "data-testid": isLatestTurn ? "chat-thread-latest-turn" : "chat-thread-turn",
3787
+ style: isLatestTurn && latestTurnMinHeight > 0 ? { minHeight: `${latestTurnMinHeight}px` } : void 0,
3788
+ children: [
3789
+ turn.userMessage ? /* @__PURE__ */ jsx10(
3790
+ MessageSlot,
3791
+ {
3792
+ "data-testid": isLatestTurn ? "chat-latest-user-anchor" : void 0,
3793
+ style: isLatestTurn ? { scrollMarginTop: `${CHAT_THREAD_SCROLL_TOP_GAP}px` } : void 0,
3794
+ children: renderChatMessage({
3795
+ message: turn.userMessage,
3796
+ mode: activeSessionMode,
3797
+ onConfirmationSubmit,
3798
+ onQuestionnaireSubmit,
3799
+ renderMessageBlock
3800
+ })
3801
+ }
3802
+ ) : null,
3803
+ turn.responseMessages.map((message) => /* @__PURE__ */ jsx10(MessageSlot, { children: renderChatMessage({
3804
+ message,
3805
+ mode: activeSessionMode,
3806
+ onConfirmationSubmit,
3807
+ onQuestionnaireSubmit,
3808
+ renderMessageBlock
3809
+ }) }, message.id)),
3810
+ isLatestTurn && error2 ? renderErrorState({ error: error2, onRetry, retryButtonLabel }) : null
3811
+ ]
3812
+ },
3813
+ turn.id
3814
+ );
3815
+ }),
3806
3816
  !latestTurn && error2 ? renderErrorState({ error: error2, onRetry, retryButtonLabel }) : null
3807
3817
  ] }),
3808
3818
  isDetached ? /* @__PURE__ */ jsx10(ScrollToLatestOverlay, { children: /* @__PURE__ */ jsxs7(
@@ -4030,7 +4040,7 @@ var ScrollToLatestBadge = styled9.span`
4030
4040
  `;
4031
4041
 
4032
4042
  // src/components/chat-composer/index.tsx
4033
- import { useCallback as useCallback8, useEffect as useEffect8, useLayoutEffect as useLayoutEffect5, useRef as useRef11, useState as useState10 } from "react";
4043
+ import { useCallback as useCallback8, useEffect as useEffect9, useLayoutEffect as useLayoutEffect6, useRef as useRef11, useState as useState10 } from "react";
4034
4044
  import styled14 from "@emotion/styled";
4035
4045
 
4036
4046
  // ../../node_modules/.pnpm/@floating-ui+react@0.27.16_react-dom@18.3.1_react@18.3.1/node_modules/@floating-ui/react/dist/floating-ui.react.mjs
@@ -4192,7 +4202,7 @@ function getFrameElement(win) {
4192
4202
 
4193
4203
  // ../../node_modules/.pnpm/@floating-ui+react@0.27.16_react-dom@18.3.1_react@18.3.1/node_modules/@floating-ui/react/dist/floating-ui.react.utils.mjs
4194
4204
  import * as React from "react";
4195
- import { useLayoutEffect as useLayoutEffect3 } from "react";
4205
+ import { useLayoutEffect as useLayoutEffect4 } from "react";
4196
4206
 
4197
4207
  // ../../node_modules/.pnpm/@floating-ui+utils@0.2.10/node_modules/@floating-ui/utils/dist/floating-ui.utils.mjs
4198
4208
  var min = Math.min;
@@ -4679,7 +4689,7 @@ function getDocument(node) {
4679
4689
  var isClient = typeof document !== "undefined";
4680
4690
  var noop = function noop2() {
4681
4691
  };
4682
- var index = isClient ? useLayoutEffect3 : noop;
4692
+ var index = isClient ? useLayoutEffect4 : noop;
4683
4693
  var SafeReact = {
4684
4694
  ...React
4685
4695
  };
@@ -5888,12 +5898,12 @@ var computePosition2 = (reference, floating, options) => {
5888
5898
 
5889
5899
  // ../../node_modules/.pnpm/@floating-ui+react-dom@2.1.6_react-dom@18.3.1_react@18.3.1/node_modules/@floating-ui/react-dom/dist/floating-ui.react-dom.mjs
5890
5900
  import * as React2 from "react";
5891
- import { useLayoutEffect as useLayoutEffect4 } from "react";
5901
+ import { useLayoutEffect as useLayoutEffect5 } from "react";
5892
5902
  import * as ReactDOM from "react-dom";
5893
5903
  var isClient2 = typeof document !== "undefined";
5894
5904
  var noop3 = function noop4() {
5895
5905
  };
5896
- var index2 = isClient2 ? useLayoutEffect4 : noop3;
5906
+ var index2 = isClient2 ? useLayoutEffect5 : noop3;
5897
5907
  function deepEqual(a, b) {
5898
5908
  if (a === b) {
5899
5909
  return true;
@@ -6630,10 +6640,10 @@ var resolveSendSession = ({
6630
6640
  };
6631
6641
 
6632
6642
  // src/components/chat-composer/hooks/use-chat-composer.ts
6633
- import { useCallback as useCallback7, useEffect as useEffect7, useRef as useRef10, useState as useState8 } from "react";
6643
+ import { useCallback as useCallback7, useEffect as useEffect8, useRef as useRef10, useState as useState8 } from "react";
6634
6644
 
6635
6645
  // src/components/chat-composer/hooks/use-composer-attachments.ts
6636
- import { useEffect as useEffect6, useRef as useRef9, useState as useState7 } from "react";
6646
+ import { useEffect as useEffect7, useRef as useRef9, useState as useState7 } from "react";
6637
6647
  var SUPPORTED_IMAGE_MIME_TYPES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/webp"]);
6638
6648
  var MAX_COMPOSER_ATTACHMENTS = 10;
6639
6649
  var createObjectUrl = (file) => typeof URL !== "undefined" && typeof URL.createObjectURL === "function" ? URL.createObjectURL(file) : "";
@@ -6649,10 +6659,10 @@ var releaseComposerAttachments = (attachments) => {
6649
6659
  var useComposerAttachments = () => {
6650
6660
  const [attachments, setAttachments] = useState7([]);
6651
6661
  const attachmentsRef = useRef9([]);
6652
- useEffect6(() => {
6662
+ useEffect7(() => {
6653
6663
  attachmentsRef.current = attachments;
6654
6664
  }, [attachments]);
6655
- useEffect6(
6665
+ useEffect7(
6656
6666
  () => () => {
6657
6667
  releaseComposerAttachments(attachmentsRef.current);
6658
6668
  },
@@ -6825,10 +6835,10 @@ var useChatComposer = () => {
6825
6835
  setIsModelsLoading(false);
6826
6836
  }
6827
6837
  }, [modelsLoader]);
6828
- useEffect7(() => {
6838
+ useEffect8(() => {
6829
6839
  void fetchModels();
6830
6840
  }, [fetchModels]);
6831
- useEffect7(() => {
6841
+ useEffect8(() => {
6832
6842
  activeSkillsLoaderRef.current = skillsLoader;
6833
6843
  const cachedSkills = getCachedSkills(skillsLoader);
6834
6844
  setAvailableSkills(cachedSkills.skills);
@@ -6864,7 +6874,7 @@ var useChatComposer = () => {
6864
6874
  }
6865
6875
  }
6866
6876
  }, [skillsLoader]);
6867
- useEffect7(() => {
6877
+ useEffect8(() => {
6868
6878
  void fetchSkills();
6869
6879
  }, [fetchSkills]);
6870
6880
  const hasModels = availableModels.length > 0;
@@ -6878,25 +6888,25 @@ var useChatComposer = () => {
6878
6888
  const stopRequestBySessionRef = useRef10(/* @__PURE__ */ new Map());
6879
6889
  const lastRequestBySessionRef = useRef10(/* @__PURE__ */ new Map());
6880
6890
  const previousActiveSessionIdRef = useRef10(activeSessionId);
6881
- useEffect7(() => {
6891
+ useEffect8(() => {
6882
6892
  setSelectedModel(
6883
6893
  (current) => resolveSelectedChatModel({ currentModel: current, availableModels, isModelsLoading })
6884
6894
  );
6885
6895
  }, [availableModels, isModelsLoading]);
6886
- useEffect7(() => {
6896
+ useEffect8(() => {
6887
6897
  if (activeSession) {
6888
6898
  setSelectedModeLocal(activeSession.mode ?? DEFAULT_CHAT_AGENT_MODE);
6889
6899
  return;
6890
6900
  }
6891
6901
  setSelectedModeLocal(preferredMode ?? DEFAULT_CHAT_AGENT_MODE);
6892
6902
  }, [activeSession, preferredMode]);
6893
- useEffect7(() => {
6903
+ useEffect8(() => {
6894
6904
  if (previousActiveSessionIdRef.current !== activeSessionId) {
6895
6905
  setSelectedSkills([]);
6896
6906
  previousActiveSessionIdRef.current = activeSessionId;
6897
6907
  }
6898
6908
  }, [activeSessionId]);
6899
- useEffect7(() => {
6909
+ useEffect8(() => {
6900
6910
  if (!attachmentNotice)
6901
6911
  return;
6902
6912
  const timeoutId = window.setTimeout(
@@ -7181,6 +7191,9 @@ var useChatComposer = () => {
7181
7191
  }
7182
7192
  if (isDraftChatSessionId(sessionId)) {
7183
7193
  finalizeStop(sessionId);
7194
+ void transport.terminateStream(void 0).catch((err) => {
7195
+ console.error("Failed to terminate draft chat session", err);
7196
+ });
7184
7197
  return;
7185
7198
  }
7186
7199
  requestStopStreaming(sessionId);
@@ -8026,7 +8039,7 @@ var ChatComposerView = ({
8026
8039
  [refs]
8027
8040
  );
8028
8041
  const activeSkillIndex = activeSkillNavigation.queryKey === activeSkillQueryKey ? activeSkillNavigation.index : 0;
8029
- useLayoutEffect5(() => {
8042
+ useLayoutEffect6(() => {
8030
8043
  const element = inputRef.current;
8031
8044
  if (!element) {
8032
8045
  return;
@@ -8264,7 +8277,7 @@ var ChatComposer = () => {
8264
8277
  const { labels, sendRef, retryRef, stopRef, enableImageAttachments } = useChatContext();
8265
8278
  const { state, actions } = useChatComposer();
8266
8279
  const { send, retry } = actions;
8267
- useEffect8(() => {
8280
+ useEffect9(() => {
8268
8281
  sendRef.current = send;
8269
8282
  retryRef.current = async (sessionId) => {
8270
8283
  retry(sessionId);
@@ -8779,7 +8792,7 @@ var AiChatWorkspaceContent = ({
8779
8792
  })
8780
8793
  );
8781
8794
  const shouldShowComposerOnly = showComposerOnlyBeforeFirstMessage && !showConversationList && !isConversationStarted;
8782
- useEffect9(() => {
8795
+ useEffect10(() => {
8783
8796
  onConversationStartedChange?.(isConversationStarted);
8784
8797
  }, [isConversationStarted, onConversationStartedChange]);
8785
8798
  return /* @__PURE__ */ jsxs14(Root, { "data-testid": "ai-chat", children: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xinghunm/ai-chat",
3
- "version": "1.3.1",
3
+ "version": "1.3.3",
4
4
  "description": "AI chat React component library",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",