@xinghunm/ai-chat 0.2.1 → 0.3.0

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.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/components/ai-chat/index.tsx
2
- import styled18 from "@emotion/styled";
2
+ import styled17 from "@emotion/styled";
3
3
  import { ConfigProvider } from "@xinghunm/compass-ui";
4
4
 
5
5
  // src/components/ai-chat-provider/index.tsx
@@ -16,6 +16,7 @@ import { createStore } from "zustand/vanilla";
16
16
  // src/types/index.ts
17
17
  var CHAT_AGENT_MODES = ["ask", "plan", "agent"];
18
18
  var DEFAULT_CHAT_AGENT_MODE = "agent";
19
+ var CHAT_MESSAGE_RENDER_ORDERS = ["blocks-first", "timeline"];
19
20
  var DEFAULT_AI_CHAT_LABELS = {
20
21
  sendButton: "Send",
21
22
  stopButton: "Stop",
@@ -517,7 +518,14 @@ var createDefaultChatTransport = ({
517
518
  // src/components/ai-chat-provider/index.tsx
518
519
  import { jsx } from "@emotion/react/jsx-runtime";
519
520
  var AiChatProvider = (props) => {
520
- const { defaultMode, labels, renderMessageBlock, enableImageAttachments = true, children } = props;
521
+ const {
522
+ defaultMode,
523
+ labels,
524
+ renderMessageBlock,
525
+ messageRenderOrder,
526
+ enableImageAttachments = true,
527
+ children
528
+ } = props;
521
529
  const [store] = useState(
522
530
  () => createChatStore(defaultMode ? { preferredMode: defaultMode } : void 0)
523
531
  );
@@ -569,6 +577,7 @@ var AiChatProvider = (props) => {
569
577
  sendRef,
570
578
  retryRef,
571
579
  renderMessageBlock,
580
+ messageRenderOrder,
572
581
  transformStreamPacket: defaultTransformStreamPacket,
573
582
  enableImageAttachments
574
583
  }),
@@ -579,6 +588,7 @@ var AiChatProvider = (props) => {
579
588
  defaultTransformStreamPacket,
580
589
  enableImageAttachments,
581
590
  labels,
591
+ messageRenderOrder,
582
592
  renderMessageBlock,
583
593
  sendRef,
584
594
  retryRef,
@@ -590,8 +600,8 @@ var AiChatProvider = (props) => {
590
600
  };
591
601
 
592
602
  // src/components/chat-thread/index.tsx
593
- import { useCallback as useCallback2, useLayoutEffect, useMemo as useMemo3, useRef as useRef4, useState as useState4 } from "react";
594
- import styled10 from "@emotion/styled";
603
+ import { useCallback as useCallback2, useLayoutEffect, useMemo as useMemo4, useRef as useRef5, useState as useState5 } from "react";
604
+ import styled9 from "@emotion/styled";
595
605
 
596
606
  // src/context/use-chat-context.ts
597
607
  import { useContext } from "react";
@@ -609,25 +619,9 @@ var useChatStore = (selector) => {
609
619
 
610
620
  // src/components/chat-thread/lib/chat-thread.ts
611
621
  var CHAT_THREAD_SCROLL_TOP_GAP = 16;
612
- var findLatestUserMessageId = (historyMessages) => {
613
- for (let index = historyMessages.length - 1; index >= 0; index -= 1) {
614
- if (historyMessages[index]?.role === "user") {
615
- return historyMessages[index]?.id;
616
- }
617
- }
618
- return void 0;
619
- };
620
- var calculateChatThreadScrollSpacerHeight = ({
621
- containerClientHeight,
622
- containerScrollHeight,
623
- targetOffsetTop
624
- }) => Math.max(
625
- 0,
626
- targetOffsetTop - CHAT_THREAD_SCROLL_TOP_GAP - (containerScrollHeight - containerClientHeight)
627
- );
628
622
 
629
623
  // src/components/chat-thread/components/chat-message-item.tsx
630
- import { Fragment, memo, useState as useState3 } from "react";
624
+ import { Fragment, memo, useState as useState4 } from "react";
631
625
  import styled7 from "@emotion/styled";
632
626
  import { keyframes } from "@emotion/react";
633
627
  import ReactMarkdown from "react-markdown";
@@ -856,11 +850,319 @@ var useChatMessageReveal = (message) => {
856
850
  }, [batchedTargetUnitCount, displayedUnitCount, isAssistantStreaming, message.role]);
857
851
  const settledContent = isFreshBlockActive ? contentBlocks.slice(0, -1).join("\n\n") : displayedContent;
858
852
  const freshContent = isFreshBlockActive ? contentBlocks[contentBlocks.length - 1] ?? "" : "";
853
+ const displayedBlocks = isAssistantStreaming && contentBlocks.length > 1 ? contentBlocks.map((content, index) => ({
854
+ content,
855
+ tone: isFreshBlockActive && index === contentBlocks.length - 1 ? "fresh" : "settled"
856
+ })) : [
857
+ {
858
+ content: displayedContent,
859
+ tone: "settled"
860
+ }
861
+ ];
859
862
  return {
860
863
  isAssistantStreaming,
864
+ isFreshBlockActive,
861
865
  displayedContent,
862
866
  settledContent,
863
- freshContent
867
+ freshContent,
868
+ displayedBlocks
869
+ };
870
+ };
871
+
872
+ // src/components/chat-thread/hooks/use-timeline-block-anchors.ts
873
+ import { useEffect as useEffect2, useMemo as useMemo3, useRef as useRef3, useState as useState2 } from "react";
874
+
875
+ // src/components/chat-thread/lib/chat-message-timeline.ts
876
+ var stringifyTimelineKeyPart = (value) => {
877
+ if (value === null || value === void 0) {
878
+ return String(value);
879
+ }
880
+ if (Array.isArray(value)) {
881
+ return `[${value.map((item) => stringifyTimelineKeyPart(item)).join(",")}]`;
882
+ }
883
+ if (typeof value === "object") {
884
+ return `{${Object.entries(value).sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey)).map(([key, nestedValue]) => `${key}:${stringifyTimelineKeyPart(nestedValue)}`).join(",")}}`;
885
+ }
886
+ return String(value);
887
+ };
888
+ var getTimelineBlockKey = (block, index) => {
889
+ switch (block.type) {
890
+ case "markdown":
891
+ return null;
892
+ case "notice":
893
+ return `${index}:notice:${block.tone}:${block.text}`;
894
+ case "parameter_summary":
895
+ return `${index}:parameter_summary:${block.items.map((item) => `${item.label}:${item.value}:${item.fieldPath ?? ""}`).join("|")}`;
896
+ case "confirmation_card":
897
+ return `${index}:confirmation_card:${block.proposal.proposalId}`;
898
+ case "result_summary":
899
+ return `${index}:result_summary:${block.summary.taskId}:${block.summary.status}`;
900
+ case "questionnaire":
901
+ return `${index}:questionnaire:${block.questionnaire.questionnaireId}`;
902
+ case "custom":
903
+ return `${index}:custom:${block.kind}:${stringifyTimelineKeyPart(block.data)}`;
904
+ default:
905
+ return null;
906
+ }
907
+ };
908
+ var getTimelineConsumedText = (blocks) => blocks.filter(
909
+ (block) => block.type === "markdown"
910
+ ).map((block) => block.text).join("\n\n");
911
+ var getTimelineTextStream = (content, blocks) => {
912
+ const consumedText = getTimelineConsumedText(blocks);
913
+ if (consumedText.length > 0 && content.startsWith(consumedText)) {
914
+ return content.slice(consumedText.length);
915
+ }
916
+ return content;
917
+ };
918
+ var buildTimelineTextDisplay = (content, isAssistantStreaming, isFreshBlockActive = isAssistantStreaming) => {
919
+ const contentBlocks = splitMarkdownBlocks(content);
920
+ const settledContent = isAssistantStreaming && isFreshBlockActive && contentBlocks.length > 1 ? contentBlocks.slice(0, -1).join("\n\n") : content;
921
+ const freshContent = isAssistantStreaming && isFreshBlockActive && contentBlocks.length > 1 ? contentBlocks[contentBlocks.length - 1] ?? "" : "";
922
+ const displayedBlocks = contentBlocks.length > 1 ? contentBlocks.map((blockContent, index) => ({
923
+ content: blockContent,
924
+ tone: isAssistantStreaming && isFreshBlockActive && freshContent && index === contentBlocks.length - 1 ? "fresh" : "settled"
925
+ })) : [{ content, tone: "settled" }];
926
+ return {
927
+ settledContent,
928
+ freshContent,
929
+ displayedBlocks
930
+ };
931
+ };
932
+ var getTimelineDisplayUnitCount = (content) => splitMarkdownBlocks(content).reduce((count, block) => count + Array.from(block).length, 0);
933
+ var buildAnchoredTimelineSegments = ({
934
+ blocks,
935
+ timelineBlockAnchors,
936
+ timelineDisplayedBlocks,
937
+ visibleTimelineBlockKeys
938
+ }) => {
939
+ const orderedTimelineSegments = [];
940
+ const totalTimelineUnits = timelineDisplayedBlocks.reduce(
941
+ (count, block) => count + Array.from(block.content).length,
942
+ 0
943
+ );
944
+ let textCursor = 0;
945
+ const buildTextSegment = (start, end, options) => {
946
+ if (end <= start) {
947
+ return null;
948
+ }
949
+ const displayedBlocks = [];
950
+ let blockCursor = 0;
951
+ for (const block of timelineDisplayedBlocks) {
952
+ const blockUnits = Array.from(block.content);
953
+ const blockStart = blockCursor;
954
+ const blockEnd = blockCursor + blockUnits.length;
955
+ if (blockEnd <= start) {
956
+ blockCursor = blockEnd;
957
+ continue;
958
+ }
959
+ if (blockStart >= end) {
960
+ break;
961
+ }
962
+ const sliceStart = Math.max(0, start - blockStart);
963
+ const sliceEnd = Math.min(blockUnits.length, end - blockStart);
964
+ const slicedContent = blockUnits.slice(sliceStart, sliceEnd).join("");
965
+ if (slicedContent) {
966
+ displayedBlocks.push({
967
+ content: slicedContent,
968
+ tone: options?.forceSettled ? "settled" : block.tone
969
+ });
970
+ }
971
+ blockCursor = blockEnd;
972
+ }
973
+ const content = displayedBlocks.map((block) => block.content).join("\n\n");
974
+ if (!content) {
975
+ return null;
976
+ }
977
+ return {
978
+ type: "text",
979
+ content,
980
+ displayedBlocks,
981
+ useTimelineSegmentation: true
982
+ };
983
+ };
984
+ let trailingCutoff = totalTimelineUnits;
985
+ for (const [index, block] of blocks.entries()) {
986
+ if (block.type === "markdown") {
987
+ orderedTimelineSegments.push({
988
+ type: "markdown",
989
+ content: block.text
990
+ });
991
+ continue;
992
+ }
993
+ const blockKey = getTimelineBlockKey(block, index);
994
+ const anchor = blockKey !== null ? timelineBlockAnchors[blockKey] ?? totalTimelineUnits : totalTimelineUnits;
995
+ const isBlockVisible = blockKey !== null && visibleTimelineBlockKeys?.[blockKey] ? true : anchor <= totalTimelineUnits;
996
+ if (anchor > textCursor) {
997
+ const textSegment = buildTextSegment(textCursor, Math.min(anchor, totalTimelineUnits), {
998
+ forceSettled: isBlockVisible
999
+ });
1000
+ if (textSegment) {
1001
+ orderedTimelineSegments.push(textSegment);
1002
+ }
1003
+ }
1004
+ if (!isBlockVisible) {
1005
+ textCursor = Math.min(anchor, totalTimelineUnits);
1006
+ trailingCutoff = Math.min(trailingCutoff, textCursor);
1007
+ continue;
1008
+ }
1009
+ trailingCutoff = totalTimelineUnits;
1010
+ orderedTimelineSegments.push({
1011
+ type: "block",
1012
+ block,
1013
+ index
1014
+ });
1015
+ textCursor = Math.max(textCursor, anchor);
1016
+ }
1017
+ const trailingTextSegment = buildTextSegment(textCursor, trailingCutoff);
1018
+ if (trailingTextSegment) {
1019
+ orderedTimelineSegments.push(trailingTextSegment);
1020
+ }
1021
+ return orderedTimelineSegments;
1022
+ };
1023
+
1024
+ // src/components/chat-thread/hooks/use-timeline-block-anchors.ts
1025
+ var useTimelineBlockAnchors = ({
1026
+ blocks,
1027
+ displayedTimelineTextLength,
1028
+ isAssistantStreaming,
1029
+ message,
1030
+ messageRenderOrder
1031
+ }) => {
1032
+ const [timelineBlockAnchors, setTimelineBlockAnchors] = useState2({});
1033
+ const [visibleTimelineBlockKeys, setVisibleTimelineBlockKeys] = useState2({});
1034
+ const currentTimelineBlockKeys = useMemo3(
1035
+ () => blocks.map((block, index) => getTimelineBlockKey(block, index)).filter((blockKey) => Boolean(blockKey)),
1036
+ [blocks]
1037
+ );
1038
+ const timelineTextStreamLength = useMemo3(
1039
+ () => getTimelineDisplayUnitCount(getTimelineTextStream(message.content, blocks)),
1040
+ [blocks, message.content]
1041
+ );
1042
+ const previousTimelineStateRef = useRef3({
1043
+ messageId: message.id,
1044
+ blockKeys: currentTimelineBlockKeys,
1045
+ textLength: timelineTextStreamLength
1046
+ });
1047
+ const effectiveTimelineBlockAnchors = useMemo3(() => {
1048
+ if (messageRenderOrder !== "timeline" || !isAssistantStreaming) {
1049
+ return timelineBlockAnchors;
1050
+ }
1051
+ const previousTimelineState = previousTimelineStateRef.current;
1052
+ const previousBlockKeys = new Set(previousTimelineState.blockKeys);
1053
+ return currentTimelineBlockKeys.reduce(
1054
+ (acc, blockKey) => {
1055
+ const existingAnchor = timelineBlockAnchors[blockKey];
1056
+ if (existingAnchor !== void 0) {
1057
+ acc[blockKey] = existingAnchor;
1058
+ return acc;
1059
+ }
1060
+ if (!previousBlockKeys.has(blockKey)) {
1061
+ acc[blockKey] = timelineTextStreamLength;
1062
+ }
1063
+ return acc;
1064
+ },
1065
+ { ...timelineBlockAnchors }
1066
+ );
1067
+ }, [
1068
+ currentTimelineBlockKeys,
1069
+ isAssistantStreaming,
1070
+ messageRenderOrder,
1071
+ timelineBlockAnchors,
1072
+ timelineTextStreamLength
1073
+ ]);
1074
+ useEffect2(() => {
1075
+ const previousTimelineState = previousTimelineStateRef.current;
1076
+ if (previousTimelineState.messageId !== message.id) {
1077
+ if (Object.keys(timelineBlockAnchors).length > 0) {
1078
+ setTimelineBlockAnchors({});
1079
+ }
1080
+ if (Object.keys(visibleTimelineBlockKeys).length > 0) {
1081
+ setVisibleTimelineBlockKeys({});
1082
+ }
1083
+ previousTimelineStateRef.current = {
1084
+ messageId: message.id,
1085
+ blockKeys: currentTimelineBlockKeys,
1086
+ textLength: timelineTextStreamLength
1087
+ };
1088
+ return;
1089
+ }
1090
+ if (messageRenderOrder === "timeline" && isAssistantStreaming) {
1091
+ const previousBlockKeys = new Set(previousTimelineState.blockKeys);
1092
+ const nextAnchors = currentTimelineBlockKeys.reduce(
1093
+ (acc, blockKey) => {
1094
+ const existingAnchor = timelineBlockAnchors[blockKey];
1095
+ if (existingAnchor !== void 0) {
1096
+ acc[blockKey] = existingAnchor;
1097
+ return acc;
1098
+ }
1099
+ if (!previousBlockKeys.has(blockKey)) {
1100
+ acc[blockKey] = timelineTextStreamLength;
1101
+ }
1102
+ return acc;
1103
+ },
1104
+ {}
1105
+ );
1106
+ const hasAnchorChanged = Object.keys(nextAnchors).length !== Object.keys(timelineBlockAnchors).length || Object.entries(nextAnchors).some(
1107
+ ([blockKey, anchor]) => timelineBlockAnchors[blockKey] !== anchor
1108
+ );
1109
+ if (hasAnchorChanged) {
1110
+ setTimelineBlockAnchors(nextAnchors);
1111
+ }
1112
+ } else if (messageRenderOrder !== "timeline" && Object.keys(timelineBlockAnchors).length > 0) {
1113
+ setTimelineBlockAnchors({});
1114
+ }
1115
+ previousTimelineStateRef.current = {
1116
+ messageId: message.id,
1117
+ blockKeys: currentTimelineBlockKeys,
1118
+ textLength: timelineTextStreamLength
1119
+ };
1120
+ }, [
1121
+ currentTimelineBlockKeys,
1122
+ isAssistantStreaming,
1123
+ message.id,
1124
+ message.content,
1125
+ messageRenderOrder,
1126
+ timelineBlockAnchors,
1127
+ timelineTextStreamLength,
1128
+ visibleTimelineBlockKeys
1129
+ ]);
1130
+ useEffect2(() => {
1131
+ if (messageRenderOrder !== "timeline") {
1132
+ if (Object.keys(visibleTimelineBlockKeys).length > 0) {
1133
+ setVisibleTimelineBlockKeys({});
1134
+ }
1135
+ return;
1136
+ }
1137
+ const nextVisibleBlockKeys = currentTimelineBlockKeys.reduce(
1138
+ (acc, blockKey) => {
1139
+ if (visibleTimelineBlockKeys[blockKey]) {
1140
+ acc[blockKey] = true;
1141
+ return acc;
1142
+ }
1143
+ const anchor = effectiveTimelineBlockAnchors[blockKey];
1144
+ if (anchor !== void 0 && anchor <= displayedTimelineTextLength) {
1145
+ acc[blockKey] = true;
1146
+ }
1147
+ return acc;
1148
+ },
1149
+ {}
1150
+ );
1151
+ const hasVisibleBlockChanged = Object.keys(nextVisibleBlockKeys).length !== Object.keys(visibleTimelineBlockKeys).length || Object.keys(nextVisibleBlockKeys).some((blockKey) => !visibleTimelineBlockKeys[blockKey]);
1152
+ if (hasVisibleBlockChanged) {
1153
+ setVisibleTimelineBlockKeys(nextVisibleBlockKeys);
1154
+ }
1155
+ }, [
1156
+ currentTimelineBlockKeys,
1157
+ displayedTimelineTextLength,
1158
+ effectiveTimelineBlockAnchors,
1159
+ messageRenderOrder,
1160
+ timelineBlockAnchors,
1161
+ visibleTimelineBlockKeys
1162
+ ]);
1163
+ return {
1164
+ timelineBlockAnchors: effectiveTimelineBlockAnchors,
1165
+ visibleTimelineBlockKeys
864
1166
  };
865
1167
  };
866
1168
 
@@ -1074,7 +1376,7 @@ var Value = styled3.span`
1074
1376
  `;
1075
1377
 
1076
1378
  // src/components/chat-thread/components/pde-ai-questionnaire-card.tsx
1077
- import { useState as useState2 } from "react";
1379
+ import { useState as useState3 } from "react";
1078
1380
  import styled4 from "@emotion/styled";
1079
1381
  import { jsx as jsx5, jsxs as jsxs3 } from "@emotion/react/jsx-runtime";
1080
1382
  var OTHER_OPTION_VALUE = "__other__";
@@ -1176,10 +1478,10 @@ var PDEAIQuestionnaireCardInner = ({
1176
1478
  interactive = false,
1177
1479
  onSubmit
1178
1480
  }) => {
1179
- const [answers, setAnswers] = useState2(
1481
+ const [answers, setAnswers] = useState3(
1180
1482
  () => createInitialAnswers(questionnaire)
1181
1483
  );
1182
- const [errorMessage, setErrorMessage] = useState2(null);
1484
+ const [errorMessage, setErrorMessage] = useState3(null);
1183
1485
  const handleSubmit = () => {
1184
1486
  const missingQuestions = questionnaire.questions.filter(
1185
1487
  (question) => question.required && isMissingRequiredAnswer(question, answers)
@@ -1615,7 +1917,7 @@ var Detail = styled5.li`
1615
1917
 
1616
1918
  // src/components/chat-thread/components/image-viewer.tsx
1617
1919
  import styled6 from "@emotion/styled";
1618
- import { useEffect as useEffect2, useRef as useRef3 } from "react";
1920
+ import { useEffect as useEffect3, useRef as useRef4 } from "react";
1619
1921
  import { jsx as jsx7 } from "@emotion/react/jsx-runtime";
1620
1922
  var Overlay = styled6.div`
1621
1923
  position: fixed;
@@ -1634,8 +1936,8 @@ var Img = styled6.img`
1634
1936
  border-radius: 4px;
1635
1937
  `;
1636
1938
  var ImageViewer = ({ src, alt, onClose }) => {
1637
- const overlayRef = useRef3(null);
1638
- useEffect2(() => {
1939
+ const overlayRef = useRef4(null);
1940
+ useEffect3(() => {
1639
1941
  const handleKey = (e) => {
1640
1942
  if (e.key === "Escape")
1641
1943
  onClose();
@@ -1643,7 +1945,7 @@ var ImageViewer = ({ src, alt, onClose }) => {
1643
1945
  document.addEventListener("keydown", handleKey);
1644
1946
  return () => document.removeEventListener("keydown", handleKey);
1645
1947
  }, [onClose]);
1646
- useEffect2(() => {
1948
+ useEffect3(() => {
1647
1949
  overlayRef.current?.focus();
1648
1950
  }, []);
1649
1951
  const stopPropagation = (e) => e.stopPropagation();
@@ -1806,9 +2108,16 @@ var ChatMessageItemView = ({
1806
2108
  onQuestionnaireSubmit,
1807
2109
  renderMessageBlock
1808
2110
  }) => {
1809
- const { labels } = useChatContext();
1810
- const [activeImage, setActiveImage] = useState3(void 0);
1811
- const { displayedContent, freshContent, isAssistantStreaming, settledContent } = useChatMessageReveal(message);
2111
+ const { labels, messageRenderOrder = "blocks-first" } = useChatContext();
2112
+ const [activeImage, setActiveImage] = useState4(void 0);
2113
+ const {
2114
+ displayedBlocks,
2115
+ displayedContent,
2116
+ freshContent,
2117
+ isAssistantStreaming,
2118
+ isFreshBlockActive,
2119
+ settledContent
2120
+ } = useChatMessageReveal(message);
1812
2121
  const isStoppedAssistant = message.role === "assistant" && message.status === "stopped";
1813
2122
  const attachments = message.attachments ?? [];
1814
2123
  const blocks = message.blocks ?? [];
@@ -1820,6 +2129,22 @@ var ChatMessageItemView = ({
1820
2129
  const canSubmitConfirmation = isPlanMode && typeof onConfirmationSubmit === "function";
1821
2130
  const canSubmitQuestionnaire = isPlanMode && typeof onQuestionnaireSubmit === "function";
1822
2131
  const shouldShowStreamingCaret = isAssistantStreaming && (!shouldRenderStructuredBlocks || hasTextContent);
2132
+ const timelineConsumedText = messageRenderOrder === "timeline" ? getTimelineConsumedText(blocks) : "";
2133
+ const hasConsumedTimelineText = timelineConsumedText.length > 0 && displayedContent.startsWith(timelineConsumedText);
2134
+ const timelineDisplayedContent = hasConsumedTimelineText ? displayedContent.slice(timelineConsumedText.length) : displayedContent;
2135
+ const timelineTextDisplay = buildTimelineTextDisplay(
2136
+ timelineDisplayedContent,
2137
+ isAssistantStreaming,
2138
+ isFreshBlockActive
2139
+ );
2140
+ const displayedTimelineTextLength = getTimelineDisplayUnitCount(timelineDisplayedContent);
2141
+ const { timelineBlockAnchors, visibleTimelineBlockKeys } = useTimelineBlockAnchors({
2142
+ blocks,
2143
+ displayedTimelineTextLength,
2144
+ isAssistantStreaming,
2145
+ message,
2146
+ messageRenderOrder
2147
+ });
1823
2148
  const renderChatMessageBlock = (block, index) => {
1824
2149
  switch (block.type) {
1825
2150
  case "markdown":
@@ -1876,11 +2201,69 @@ var ChatMessageItemView = ({
1876
2201
  return null;
1877
2202
  }
1878
2203
  };
1879
- const renderTextContent = () => /* @__PURE__ */ jsxs5(Fragment2, { children: [
1880
- settledContent ? /* @__PURE__ */ jsx8(ContentBlock, { "data-testid": "chat-message-settled-block", "data-block-tone": "settled", children: renderMarkdownContent(settledContent) }) : null,
1881
- freshContent ? /* @__PURE__ */ jsx8(ContentBlock, { "data-testid": "chat-message-fresh-block", "data-block-tone": "fresh", children: renderMarkdownContent(freshContent) }) : null,
1882
- !settledContent && !freshContent && hasTextContent ? /* @__PURE__ */ jsx8(ContentBlock, { "data-testid": "chat-message-settled-block", "data-block-tone": "settled", children: renderMarkdownContent(displayedContent) }) : null
1883
- ] });
2204
+ const renderTextContent = (options) => {
2205
+ const textContent = options?.content ?? displayedContent;
2206
+ const localTimelineTextDisplay = options?.displayedBlocks ? void 0 : options?.useTimelineSegmentation && options.content !== void 0 ? buildTimelineTextDisplay(options.content, isAssistantStreaming, isFreshBlockActive) : void 0;
2207
+ const textBlocks = options?.displayedBlocks ?? localTimelineTextDisplay?.displayedBlocks ?? displayedBlocks;
2208
+ const settledText = localTimelineTextDisplay?.settledContent ?? settledContent;
2209
+ const freshText = localTimelineTextDisplay?.freshContent ?? freshContent;
2210
+ return /* @__PURE__ */ jsxs5(Fragment2, { children: [
2211
+ textBlocks.filter((block) => block.content).map((block, index) => /* @__PURE__ */ jsx8(
2212
+ ContentBlock,
2213
+ {
2214
+ "data-testid": block.tone === "fresh" ? "chat-message-fresh-block" : "chat-message-settled-block",
2215
+ "data-block-tone": block.tone,
2216
+ "data-block-index": index,
2217
+ children: renderMarkdownContent(block.content)
2218
+ },
2219
+ `${block.tone}-${index}`
2220
+ )),
2221
+ !textBlocks.some((block) => block.content) && !settledText && !freshText && Boolean(textContent) ? /* @__PURE__ */ jsx8(ContentBlock, { "data-testid": "chat-message-settled-block", "data-block-tone": "settled", children: renderMarkdownContent(textContent) }) : null
2222
+ ] });
2223
+ };
2224
+ const renderStaticTextSegment = (content) => /* @__PURE__ */ jsx8(ContentBlock, { "data-testid": "chat-message-settled-block", "data-block-tone": "settled", children: renderMarkdownContent(content) });
2225
+ const bodySegments = (() => {
2226
+ if (!shouldRenderStructuredBlocks && hasTextContent) {
2227
+ return [{ type: "text" }];
2228
+ }
2229
+ if (!shouldRenderStructuredBlocks) {
2230
+ return [];
2231
+ }
2232
+ if (messageRenderOrder === "timeline" && hasTextContent) {
2233
+ const hasAnchoredStructuredBlocks = blocks.some((block, index) => {
2234
+ const blockKey = getTimelineBlockKey(block, index);
2235
+ return blockKey ? timelineBlockAnchors[blockKey] !== void 0 : false;
2236
+ });
2237
+ if (hasAnchoredStructuredBlocks) {
2238
+ return buildAnchoredTimelineSegments({
2239
+ blocks,
2240
+ timelineBlockAnchors,
2241
+ timelineDisplayedBlocks: timelineTextDisplay.displayedBlocks,
2242
+ visibleTimelineBlockKeys
2243
+ });
2244
+ }
2245
+ const orderedTimelineSegments = blocks.map(
2246
+ (block, index) => block.type === "markdown" ? {
2247
+ type: "markdown",
2248
+ content: block.text
2249
+ } : {
2250
+ type: "block",
2251
+ block,
2252
+ index
2253
+ }
2254
+ );
2255
+ if (!timelineConsumedText) {
2256
+ return displayedContent ? [{ type: "text", content: displayedContent }, ...orderedTimelineSegments] : orderedTimelineSegments;
2257
+ }
2258
+ return timelineDisplayedContent ? [...orderedTimelineSegments, { type: "text", content: timelineDisplayedContent }] : orderedTimelineSegments;
2259
+ }
2260
+ const orderedBlocks = blocks.map((block, index) => ({
2261
+ type: "block",
2262
+ block,
2263
+ index
2264
+ }));
2265
+ return hasTextContent ? [...orderedBlocks, { type: "text" }] : orderedBlocks;
2266
+ })();
1884
2267
  return /* @__PURE__ */ jsxs5(Fragment2, { children: [
1885
2268
  /* @__PURE__ */ jsxs5(Bubble, { "data-role": message.role, "data-status": message.status ?? "done", children: [
1886
2269
  /* @__PURE__ */ jsxs5(Header2, { children: [
@@ -1899,17 +2282,18 @@ var ChatMessageItemView = ({
1899
2282
  isStoppedAssistant ? /* @__PURE__ */ jsx8(StatusTag, { "data-testid": "chat-message-stopped-tag", children: labels.stoppedResponse }) : null
1900
2283
  ] }),
1901
2284
  /* @__PURE__ */ jsxs5(Content, { "data-testid": "chat-message-content", children: [
1902
- shouldRenderStructuredBlocks || hasTextContent ? /* @__PURE__ */ jsxs5(ContentStack, { "data-testid": "chat-message-body-stack", children: [
1903
- shouldRenderStructuredBlocks ? blocks.map((block, index) => /* @__PURE__ */ jsx8(
1904
- ContentSegment,
1905
- {
1906
- "data-testid": "chat-message-content-segment",
1907
- children: renderChatMessageBlock(block, index)
1908
- },
1909
- `${block.type}-${index}`
1910
- )) : null,
1911
- hasTextContent ? /* @__PURE__ */ jsx8(ContentSegment, { "data-testid": "chat-message-content-segment", children: renderTextContent() }) : null
1912
- ] }) : null,
2285
+ shouldRenderStructuredBlocks || hasTextContent ? /* @__PURE__ */ jsx8(ContentStack, { "data-testid": "chat-message-body-stack", children: bodySegments.map((segment, index) => /* @__PURE__ */ jsx8(
2286
+ ContentSegment,
2287
+ {
2288
+ "data-testid": "chat-message-content-segment",
2289
+ children: segment.type === "block" ? renderChatMessageBlock(segment.block, segment.index) : segment.type === "text" ? segment.content !== void 0 ? segment.useTimelineSegmentation ? renderTextContent({
2290
+ content: segment.content,
2291
+ displayedBlocks: segment.displayedBlocks,
2292
+ useTimelineSegmentation: true
2293
+ }) : renderStaticTextSegment(segment.content) : renderTextContent() : renderStaticTextSegment(segment.content)
2294
+ },
2295
+ segment.type === "text" ? `text-${index}` : segment.type === "markdown" ? `markdown-${index}` : `${segment.block.type}-${segment.index}`
2296
+ )) }) : null,
1913
2297
  attachments.length ? /* @__PURE__ */ jsx8(AttachmentGrid, { "data-testid": "chat-message-attachment-grid", children: attachments.map((attachment) => /* @__PURE__ */ jsx8(
1914
2298
  AttachmentButton,
1915
2299
  {
@@ -2143,65 +2527,21 @@ var StreamingCaret = styled7.span`
2143
2527
  animation: ${caretBlink} 0.9s steps(1) infinite;
2144
2528
  `;
2145
2529
 
2146
- // src/components/chat-thread/components/chat-thread-history-list.tsx
2147
- import { memo as memo2 } from "react";
2148
- import styled8 from "@emotion/styled";
2149
- import { jsx as jsx9 } from "@emotion/react/jsx-runtime";
2150
- var ChatThreadHistoryList = memo2(
2151
- ({
2152
- mode,
2153
- historyMessages,
2154
- latestUserMessageId,
2155
- latestUserMessageRef,
2156
- onConfirmationSubmit,
2157
- onQuestionnaireSubmit,
2158
- renderMessageBlock
2159
- }) => /* @__PURE__ */ jsx9(HistoryGroup, { "data-testid": "chat-thread-history", children: historyMessages.map((message) => /* @__PURE__ */ jsx9(
2160
- MessageSlot,
2161
- {
2162
- ref: message.id === latestUserMessageId ? latestUserMessageRef : null,
2163
- "data-testid": message.id === latestUserMessageId ? "chat-latest-user-anchor" : void 0,
2164
- style: message.id === latestUserMessageId ? {
2165
- scrollMarginTop: `${CHAT_THREAD_SCROLL_TOP_GAP}px`
2166
- } : void 0,
2167
- children: /* @__PURE__ */ jsx9(
2168
- ChatMessageItem,
2169
- {
2170
- mode,
2171
- message,
2172
- onConfirmationSubmit,
2173
- onQuestionnaireSubmit,
2174
- renderMessageBlock
2175
- }
2176
- )
2177
- },
2178
- message.id
2179
- )) })
2180
- );
2181
- ChatThreadHistoryList.displayName = "ChatThreadHistoryList";
2182
- var HistoryGroup = styled8.div`
2183
- display: contents;
2184
- `;
2185
- var MessageSlot = styled8.div`
2186
- display: flex;
2187
- align-items: flex-start;
2188
- `;
2189
-
2190
2530
  // src/components/chat-thread/components/chat-thread-empty-state.tsx
2191
- import styled9 from "@emotion/styled";
2192
- import { jsx as jsx10, jsxs as jsxs6 } from "@emotion/react/jsx-runtime";
2531
+ import styled8 from "@emotion/styled";
2532
+ import { jsx as jsx9, jsxs as jsxs6 } from "@emotion/react/jsx-runtime";
2193
2533
  var ChatThreadEmptyState = () => {
2194
2534
  const { labels } = useChatContext();
2195
2535
  return /* @__PURE__ */ jsxs6(EmptyShell, { "data-testid": "chat-empty-hero", children: [
2196
2536
  /* @__PURE__ */ jsxs6(HeroMark, { children: [
2197
- /* @__PURE__ */ jsx10(HeroOrbit, {}),
2198
- /* @__PURE__ */ jsx10(HeroCore, { children: "AI" })
2537
+ /* @__PURE__ */ jsx9(HeroOrbit, {}),
2538
+ /* @__PURE__ */ jsx9(HeroCore, { children: "AI" })
2199
2539
  ] }),
2200
- /* @__PURE__ */ jsx10(HeroTitle, { children: labels.emptyStateTitle }),
2201
- /* @__PURE__ */ jsx10(HeroSubtitle, { children: labels.emptyStateSubtitle })
2540
+ /* @__PURE__ */ jsx9(HeroTitle, { children: labels.emptyStateTitle }),
2541
+ /* @__PURE__ */ jsx9(HeroSubtitle, { children: labels.emptyStateSubtitle })
2202
2542
  ] });
2203
2543
  };
2204
- var EmptyShell = styled9.div`
2544
+ var EmptyShell = styled8.div`
2205
2545
  flex: 1;
2206
2546
  min-height: 0;
2207
2547
  display: flex;
@@ -2211,7 +2551,7 @@ var EmptyShell = styled9.div`
2211
2551
  gap: 16px;
2212
2552
  padding: 48px 24px 24px;
2213
2553
  `;
2214
- var HeroMark = styled9.div`
2554
+ var HeroMark = styled8.div`
2215
2555
  position: relative;
2216
2556
  width: 108px;
2217
2557
  height: 108px;
@@ -2219,7 +2559,7 @@ var HeroMark = styled9.div`
2219
2559
  display: grid;
2220
2560
  place-items: center;
2221
2561
  `;
2222
- var HeroOrbit = styled9.div`
2562
+ var HeroOrbit = styled8.div`
2223
2563
  position: absolute;
2224
2564
  inset: 20px;
2225
2565
  border-radius: 50%;
@@ -2242,7 +2582,7 @@ var HeroOrbit = styled9.div`
2242
2582
  transform: rotate(-22deg);
2243
2583
  }
2244
2584
  `;
2245
- var HeroCore = styled9.div`
2585
+ var HeroCore = styled8.div`
2246
2586
  position: relative;
2247
2587
  z-index: 1;
2248
2588
  font-size: 28px;
@@ -2252,13 +2592,13 @@ var HeroCore = styled9.div`
2252
2592
  color: rgba(242, 244, 255, 0.96);
2253
2593
  text-shadow: 0 0 16px rgba(98, 116, 255, 0.65);
2254
2594
  `;
2255
- var HeroTitle = styled9.p`
2595
+ var HeroTitle = styled8.p`
2256
2596
  margin: 0;
2257
2597
  color: rgba(255, 255, 255, 0.88);
2258
2598
  font-size: 16px;
2259
2599
  line-height: 24px;
2260
2600
  `;
2261
- var HeroSubtitle = styled9.p`
2601
+ var HeroSubtitle = styled8.p`
2262
2602
  margin: 0;
2263
2603
  color: rgba(255, 255, 255, 0.72);
2264
2604
  font-size: 14px;
@@ -2266,7 +2606,74 @@ var HeroSubtitle = styled9.p`
2266
2606
  `;
2267
2607
 
2268
2608
  // src/components/chat-thread/index.tsx
2269
- import { jsx as jsx11, jsxs as jsxs7 } from "@emotion/react/jsx-runtime";
2609
+ import { jsx as jsx10, jsxs as jsxs7 } from "@emotion/react/jsx-runtime";
2610
+ var renderChatMessage = ({
2611
+ message,
2612
+ mode,
2613
+ onConfirmationSubmit,
2614
+ onQuestionnaireSubmit,
2615
+ renderMessageBlock
2616
+ }) => /* @__PURE__ */ jsx10(
2617
+ ChatMessageItem,
2618
+ {
2619
+ mode,
2620
+ message,
2621
+ onConfirmationSubmit,
2622
+ onQuestionnaireSubmit,
2623
+ renderMessageBlock
2624
+ }
2625
+ );
2626
+ var renderErrorState = ({
2627
+ error,
2628
+ onRetry,
2629
+ retryButtonLabel
2630
+ }) => /* @__PURE__ */ jsxs7(ErrorState, { "data-testid": "chat-thread-error-state", children: [
2631
+ /* @__PURE__ */ jsx10(ErrorText, { children: error }),
2632
+ onRetry ? /* @__PURE__ */ jsx10(ErrorActions, { children: /* @__PURE__ */ jsx10(RetryButton, { type: "button", "data-testid": "chat-thread-retry", onClick: onRetry, children: retryButtonLabel }) }) : null
2633
+ ] });
2634
+ var groupConversationTurns = (historyMessages, streamingMessage) => {
2635
+ const turns = [];
2636
+ let currentTurn = null;
2637
+ historyMessages.forEach((message) => {
2638
+ if (message.role === "user") {
2639
+ currentTurn = {
2640
+ id: message.id,
2641
+ userMessage: message,
2642
+ responseMessages: []
2643
+ };
2644
+ turns.push(currentTurn);
2645
+ return;
2646
+ }
2647
+ if (!currentTurn) {
2648
+ currentTurn = {
2649
+ id: `assistant-turn-${message.id}`,
2650
+ responseMessages: [message]
2651
+ };
2652
+ turns.push(currentTurn);
2653
+ return;
2654
+ }
2655
+ currentTurn.responseMessages.push(message);
2656
+ });
2657
+ if (!streamingMessage) {
2658
+ return turns;
2659
+ }
2660
+ const lastTurn = turns[turns.length - 1];
2661
+ if (lastTurn) {
2662
+ return [
2663
+ ...turns.slice(0, -1),
2664
+ {
2665
+ ...lastTurn,
2666
+ responseMessages: [...lastTurn.responseMessages, streamingMessage]
2667
+ }
2668
+ ];
2669
+ }
2670
+ return [
2671
+ {
2672
+ id: `assistant-turn-${streamingMessage.id}`,
2673
+ responseMessages: [streamingMessage]
2674
+ }
2675
+ ];
2676
+ };
2270
2677
  var ChatThreadView = ({
2271
2678
  activeSessionMode = DEFAULT_CHAT_AGENT_MODE,
2272
2679
  historyMessages,
@@ -2278,32 +2685,46 @@ var ChatThreadView = ({
2278
2685
  onQuestionnaireSubmit,
2279
2686
  renderMessageBlock
2280
2687
  }) => {
2281
- const containerRef = useRef4(null);
2282
- const latestUserMessageId = useMemo3(
2283
- () => findLatestUserMessageId(historyMessages),
2284
- [historyMessages]
2688
+ const containerRef = useRef5(null);
2689
+ const conversationTurns = useMemo4(
2690
+ () => groupConversationTurns(historyMessages, streamingMessage),
2691
+ [historyMessages, streamingMessage]
2285
2692
  );
2286
- const latestUserMessageRef = useRef4(null);
2287
- const pendingScrollUserMessageIdRef = useRef4(void 0);
2288
- const reservedSpaceFrameRef = useRef4(null);
2289
- const [latestUserMessageReservedSpace, setLatestUserMessageReservedSpace] = useState4({ messageId: void 0, value: 0 });
2290
- const reservedPaddingBottom = 24 + (latestUserMessageReservedSpace.messageId === latestUserMessageId ? latestUserMessageReservedSpace.value : 0);
2291
- const measureLatestUserMessageReservedSpace = useCallback2((messageId) => {
2693
+ const latestTurn = conversationTurns[conversationTurns.length - 1];
2694
+ const previousTurns = conversationTurns.slice(0, -1);
2695
+ const latestUserMessageId = latestTurn?.userMessage?.id;
2696
+ const latestUserMessageRef = useRef5(null);
2697
+ const reservedSpaceFrameRef = useRef5(null);
2698
+ const [latestTurnMinHeight, setLatestTurnMinHeight] = useState5(0);
2699
+ const measureLatestTurnMinHeight = useCallback2(() => {
2700
+ const container = containerRef.current;
2701
+ if (!container)
2702
+ return;
2703
+ const computedStyle = window.getComputedStyle(container);
2704
+ const paddingTop = Number.parseFloat(computedStyle.paddingTop || "0") || 0;
2705
+ const paddingBottom = Number.parseFloat(computedStyle.paddingBottom || "0") || 0;
2706
+ const nextMinHeight = Math.max(0, container.clientHeight - paddingTop - paddingBottom);
2707
+ setLatestTurnMinHeight((current) => current === nextMinHeight ? current : nextMinHeight);
2708
+ }, []);
2709
+ const scrollLatestUserMessageToTop = useCallback2(() => {
2292
2710
  const container = containerRef.current;
2293
2711
  const target = latestUserMessageRef.current;
2294
2712
  if (!container || !target)
2295
2713
  return;
2296
- const reservedHeight = calculateChatThreadScrollSpacerHeight({
2297
- containerClientHeight: container.clientHeight,
2298
- containerScrollHeight: container.scrollHeight,
2299
- targetOffsetTop: target.offsetTop
2300
- });
2301
- setLatestUserMessageReservedSpace((current) => {
2302
- const next = reservedHeight > 0 ? reservedHeight : 0;
2303
- if (current.messageId === messageId && current.value === next)
2304
- return current;
2305
- return { messageId, value: next };
2306
- });
2714
+ const containerRect = container.getBoundingClientRect();
2715
+ const targetRect = target.getBoundingClientRect();
2716
+ const nextScrollTop = Math.max(
2717
+ 0,
2718
+ container.scrollTop + (targetRect.top - containerRect.top) - CHAT_THREAD_SCROLL_TOP_GAP
2719
+ );
2720
+ if (typeof container.scrollTo === "function") {
2721
+ container.scrollTo({
2722
+ top: nextScrollTop,
2723
+ behavior: "auto"
2724
+ });
2725
+ return;
2726
+ }
2727
+ container.scrollTop = nextScrollTop;
2307
2728
  }, []);
2308
2729
  useLayoutEffect(() => {
2309
2730
  if (reservedSpaceFrameRef.current !== null) {
@@ -2311,12 +2732,9 @@ var ChatThreadView = ({
2311
2732
  reservedSpaceFrameRef.current = null;
2312
2733
  }
2313
2734
  if (!latestUserMessageId) {
2314
- pendingScrollUserMessageIdRef.current = void 0;
2315
2735
  reservedSpaceFrameRef.current = window.requestAnimationFrame(() => {
2316
2736
  reservedSpaceFrameRef.current = null;
2317
- setLatestUserMessageReservedSpace(
2318
- (current) => current.messageId === void 0 && current.value === 0 ? current : { messageId: void 0, value: 0 }
2319
- );
2737
+ setLatestTurnMinHeight((current) => current === 0 ? current : 0);
2320
2738
  });
2321
2739
  return () => {
2322
2740
  if (reservedSpaceFrameRef.current !== null) {
@@ -2325,10 +2743,10 @@ var ChatThreadView = ({
2325
2743
  }
2326
2744
  };
2327
2745
  }
2328
- pendingScrollUserMessageIdRef.current = latestUserMessageId;
2329
2746
  reservedSpaceFrameRef.current = window.requestAnimationFrame(() => {
2330
2747
  reservedSpaceFrameRef.current = null;
2331
- measureLatestUserMessageReservedSpace(latestUserMessageId);
2748
+ measureLatestTurnMinHeight();
2749
+ scrollLatestUserMessageToTop();
2332
2750
  });
2333
2751
  return () => {
2334
2752
  if (reservedSpaceFrameRef.current !== null) {
@@ -2336,51 +2754,79 @@ var ChatThreadView = ({
2336
2754
  reservedSpaceFrameRef.current = null;
2337
2755
  }
2338
2756
  };
2339
- }, [latestUserMessageId, measureLatestUserMessageReservedSpace]);
2757
+ }, [latestUserMessageId, measureLatestTurnMinHeight, scrollLatestUserMessageToTop]);
2340
2758
  useLayoutEffect(() => {
2341
- if (!latestUserMessageId || pendingScrollUserMessageIdRef.current !== latestUserMessageId)
2342
- return;
2343
- if (latestUserMessageReservedSpace.messageId !== latestUserMessageId)
2759
+ if (!latestUserMessageId)
2344
2760
  return;
2345
- latestUserMessageRef.current?.scrollIntoView({ block: "start", behavior: "smooth" });
2346
- pendingScrollUserMessageIdRef.current = void 0;
2347
- }, [latestUserMessageId, latestUserMessageReservedSpace]);
2348
- return /* @__PURE__ */ jsxs7(
2349
- Container,
2350
- {
2351
- ref: containerRef,
2352
- "data-testid": "chat-thread",
2353
- style: { paddingBottom: `${reservedPaddingBottom}px` },
2354
- children: [
2355
- /* @__PURE__ */ jsx11(
2356
- ChatThreadHistoryList,
2357
- {
2358
- mode: activeSessionMode,
2359
- historyMessages,
2360
- latestUserMessageId,
2361
- latestUserMessageRef,
2362
- onConfirmationSubmit,
2363
- onQuestionnaireSubmit,
2364
- renderMessageBlock
2365
- }
2366
- ),
2367
- streamingMessage ? /* @__PURE__ */ jsx11(StreamingGroup, { "data-testid": "chat-thread-streaming", children: /* @__PURE__ */ jsx11(MessageSlot2, { children: /* @__PURE__ */ jsx11(
2368
- ChatMessageItem,
2369
- {
2761
+ const handleResize = () => {
2762
+ measureLatestTurnMinHeight();
2763
+ scrollLatestUserMessageToTop();
2764
+ };
2765
+ const container = containerRef.current;
2766
+ let resizeObserver = null;
2767
+ if (container && typeof ResizeObserver !== "undefined") {
2768
+ resizeObserver = new ResizeObserver(() => {
2769
+ handleResize();
2770
+ });
2771
+ resizeObserver.observe(container);
2772
+ }
2773
+ window.addEventListener("resize", handleResize);
2774
+ return () => {
2775
+ resizeObserver?.disconnect();
2776
+ window.removeEventListener("resize", handleResize);
2777
+ };
2778
+ }, [latestUserMessageId, measureLatestTurnMinHeight, scrollLatestUserMessageToTop]);
2779
+ return /* @__PURE__ */ jsxs7(Container, { ref: containerRef, "data-testid": "chat-thread", children: [
2780
+ previousTurns.map((turn) => /* @__PURE__ */ jsxs7(ConversationTurn, { "data-testid": "chat-thread-turn", children: [
2781
+ turn.userMessage ? /* @__PURE__ */ jsx10(MessageSlot, { children: renderChatMessage({
2782
+ message: turn.userMessage,
2783
+ mode: activeSessionMode,
2784
+ onConfirmationSubmit,
2785
+ onQuestionnaireSubmit,
2786
+ renderMessageBlock
2787
+ }) }) : null,
2788
+ turn.responseMessages.map((message) => /* @__PURE__ */ jsx10(MessageSlot, { children: renderChatMessage({
2789
+ message,
2790
+ mode: activeSessionMode,
2791
+ onConfirmationSubmit,
2792
+ onQuestionnaireSubmit,
2793
+ renderMessageBlock
2794
+ }) }, message.id))
2795
+ ] }, turn.id)),
2796
+ latestTurn ? /* @__PURE__ */ jsxs7(
2797
+ ConversationTurn,
2798
+ {
2799
+ "data-testid": "chat-thread-latest-turn",
2800
+ style: latestTurnMinHeight > 0 ? { minHeight: `${latestTurnMinHeight}px` } : void 0,
2801
+ children: [
2802
+ latestTurn.userMessage ? /* @__PURE__ */ jsx10(
2803
+ MessageSlot,
2804
+ {
2805
+ ref: latestUserMessageRef,
2806
+ "data-testid": "chat-latest-user-anchor",
2807
+ style: { scrollMarginTop: `${CHAT_THREAD_SCROLL_TOP_GAP}px` },
2808
+ children: renderChatMessage({
2809
+ message: latestTurn.userMessage,
2810
+ mode: activeSessionMode,
2811
+ onConfirmationSubmit,
2812
+ onQuestionnaireSubmit,
2813
+ renderMessageBlock
2814
+ })
2815
+ }
2816
+ ) : null,
2817
+ latestTurn.responseMessages.map((message) => /* @__PURE__ */ jsx10(MessageSlot, { children: renderChatMessage({
2818
+ message,
2370
2819
  mode: activeSessionMode,
2371
- message: streamingMessage,
2372
2820
  onConfirmationSubmit,
2373
2821
  onQuestionnaireSubmit,
2374
2822
  renderMessageBlock
2375
- }
2376
- ) }) }) : null,
2377
- error ? /* @__PURE__ */ jsxs7(ErrorState, { "data-testid": "chat-thread-error-state", children: [
2378
- /* @__PURE__ */ jsx11(ErrorText, { children: error }),
2379
- onRetry ? /* @__PURE__ */ jsx11(ErrorActions, { children: /* @__PURE__ */ jsx11(RetryButton, { type: "button", "data-testid": "chat-thread-retry", onClick: onRetry, children: retryButtonLabel }) }) : null
2380
- ] }) : null
2381
- ]
2382
- }
2383
- );
2823
+ }) }, message.id)),
2824
+ error ? renderErrorState({ error, onRetry, retryButtonLabel }) : null
2825
+ ]
2826
+ }
2827
+ ) : null,
2828
+ !latestTurn && error ? renderErrorState({ error, onRetry, retryButtonLabel }) : null
2829
+ ] });
2384
2830
  };
2385
2831
  var EMPTY_MESSAGES = [];
2386
2832
  var ChatThread = () => {
@@ -2424,9 +2870,9 @@ var ChatThread = () => {
2424
2870
  [sendRef]
2425
2871
  );
2426
2872
  if (!hasSessions || messages.length === 0 && !streamingMessage) {
2427
- return /* @__PURE__ */ jsx11(ChatThreadEmptyState, {});
2873
+ return /* @__PURE__ */ jsx10(ChatThreadEmptyState, {});
2428
2874
  }
2429
- return /* @__PURE__ */ jsx11(
2875
+ return /* @__PURE__ */ jsx10(
2430
2876
  ChatThreadView,
2431
2877
  {
2432
2878
  activeSessionMode,
@@ -2441,7 +2887,7 @@ var ChatThread = () => {
2441
2887
  }
2442
2888
  );
2443
2889
  };
2444
- var Container = styled10.div`
2890
+ var Container = styled9.div`
2445
2891
  display: flex;
2446
2892
  flex: 1;
2447
2893
  flex-direction: column;
@@ -2464,27 +2910,29 @@ var Container = styled10.div`
2464
2910
  background: transparent;
2465
2911
  }
2466
2912
  `;
2467
- var MessageSlot2 = styled10.div`
2913
+ var MessageSlot = styled9.div`
2468
2914
  display: flex;
2469
2915
  `;
2470
- var StreamingGroup = styled10.div`
2471
- display: contents;
2916
+ var ConversationTurn = styled9.div`
2917
+ display: flex;
2918
+ flex-direction: column;
2919
+ gap: 18px;
2472
2920
  `;
2473
- var ErrorText = styled10.div`
2921
+ var ErrorText = styled9.div`
2474
2922
  color: #ff7b72;
2475
2923
  font-size: 14px;
2476
2924
  `;
2477
- var ErrorState = styled10.div`
2925
+ var ErrorState = styled9.div`
2478
2926
  display: flex;
2479
2927
  flex-direction: column;
2480
2928
  align-items: flex-start;
2481
2929
  gap: 10px;
2482
2930
  `;
2483
- var ErrorActions = styled10.div`
2931
+ var ErrorActions = styled9.div`
2484
2932
  display: flex;
2485
2933
  align-items: center;
2486
2934
  `;
2487
- var RetryButton = styled10.button`
2935
+ var RetryButton = styled9.button`
2488
2936
  border: 1px solid rgba(255, 255, 255, 0.14);
2489
2937
  border-radius: 999px;
2490
2938
  background: rgba(255, 255, 255, 0.04);
@@ -2500,8 +2948,8 @@ var RetryButton = styled10.button`
2500
2948
  `;
2501
2949
 
2502
2950
  // src/components/chat-composer/index.tsx
2503
- import { useEffect as useEffect5, useRef as useRef7 } from "react";
2504
- import styled15 from "@emotion/styled";
2951
+ import { useEffect as useEffect6, useRef as useRef8 } from "react";
2952
+ import styled14 from "@emotion/styled";
2505
2953
 
2506
2954
  // src/components/chat-composer/lib/chat-composer.ts
2507
2955
  var DRAFT_CHAT_SESSION_ID_PREFIX = "draft-session-";
@@ -2612,10 +3060,10 @@ var resolveSendSession = ({
2612
3060
  };
2613
3061
 
2614
3062
  // src/components/chat-composer/hooks/use-chat-composer.ts
2615
- import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef6, useState as useState6 } from "react";
3063
+ import { useCallback as useCallback3, useEffect as useEffect5, useRef as useRef7, useState as useState7 } from "react";
2616
3064
 
2617
3065
  // src/components/chat-composer/hooks/use-composer-attachments.ts
2618
- import { useEffect as useEffect3, useRef as useRef5, useState as useState5 } from "react";
3066
+ import { useEffect as useEffect4, useRef as useRef6, useState as useState6 } from "react";
2619
3067
  var SUPPORTED_IMAGE_MIME_TYPES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/webp"]);
2620
3068
  var MAX_COMPOSER_ATTACHMENTS = 10;
2621
3069
  var createObjectUrl = (file) => typeof URL !== "undefined" && typeof URL.createObjectURL === "function" ? URL.createObjectURL(file) : "";
@@ -2629,12 +3077,12 @@ var releaseComposerAttachments = (attachments) => {
2629
3077
  attachments.forEach((attachment) => revokeObjectUrl(attachment.previewUrl));
2630
3078
  };
2631
3079
  var useComposerAttachments = () => {
2632
- const [attachments, setAttachments] = useState5([]);
2633
- const attachmentsRef = useRef5([]);
2634
- useEffect3(() => {
3080
+ const [attachments, setAttachments] = useState6([]);
3081
+ const attachmentsRef = useRef6([]);
3082
+ useEffect4(() => {
2635
3083
  attachmentsRef.current = attachments;
2636
3084
  }, [attachments]);
2637
- useEffect3(
3085
+ useEffect4(
2638
3086
  () => () => {
2639
3087
  releaseComposerAttachments(attachmentsRef.current);
2640
3088
  },
@@ -2747,9 +3195,9 @@ var useChatComposer = () => {
2747
3195
  const clearSessionError = useChatStore((s) => s.clearSessionError);
2748
3196
  const setPreferredMode = useChatStore((s) => s.setPreferredMode);
2749
3197
  const setSessionMode = useChatStore((s) => s.setSessionMode);
2750
- const [availableModels, setAvailableModels] = useState6([]);
2751
- const [isModelsLoading, setIsModelsLoading] = useState6(true);
2752
- const [isModelsError, setIsModelsError] = useState6(false);
3198
+ const [availableModels, setAvailableModels] = useState7([]);
3199
+ const [isModelsLoading, setIsModelsLoading] = useState7(true);
3200
+ const [isModelsError, setIsModelsError] = useState7(false);
2753
3201
  const fetchModels = useCallback3(async () => {
2754
3202
  setIsModelsLoading(true);
2755
3203
  setIsModelsError(false);
@@ -2762,31 +3210,31 @@ var useChatComposer = () => {
2762
3210
  setIsModelsLoading(false);
2763
3211
  }
2764
3212
  }, [transport]);
2765
- useEffect4(() => {
3213
+ useEffect5(() => {
2766
3214
  void fetchModels();
2767
3215
  }, [fetchModels]);
2768
3216
  const hasModels = availableModels.length > 0;
2769
- const [value, setValue] = useState6("");
2770
- const [selectedModel, setSelectedModel] = useState6("");
2771
- const [selectedMode, setSelectedModeLocal] = useState6(DEFAULT_CHAT_AGENT_MODE);
2772
- const [attachmentNotice, setAttachmentNotice] = useState6(null);
3217
+ const [value, setValue] = useState7("");
3218
+ const [selectedModel, setSelectedModel] = useState7("");
3219
+ const [selectedMode, setSelectedModeLocal] = useState7(DEFAULT_CHAT_AGENT_MODE);
3220
+ const [attachmentNotice, setAttachmentNotice] = useState7(null);
2773
3221
  const { attachments, appendFiles, removeAttachment, takeMessageAttachments } = useComposerAttachments();
2774
- const abortControllerRef = useRef6(null);
2775
- const stopRequestRef = useRef6(null);
2776
- const lastRequestRef = useRef6(null);
2777
- useEffect4(() => {
3222
+ const abortControllerRef = useRef7(null);
3223
+ const stopRequestRef = useRef7(null);
3224
+ const lastRequestRef = useRef7(null);
3225
+ useEffect5(() => {
2778
3226
  setSelectedModel(
2779
3227
  (current) => resolveSelectedChatModel({ currentModel: current, availableModels, isModelsLoading })
2780
3228
  );
2781
3229
  }, [availableModels, isModelsLoading]);
2782
- useEffect4(() => {
3230
+ useEffect5(() => {
2783
3231
  if (activeSession) {
2784
3232
  setSelectedModeLocal(activeSession.mode ?? DEFAULT_CHAT_AGENT_MODE);
2785
3233
  return;
2786
3234
  }
2787
3235
  setSelectedModeLocal(preferredMode ?? DEFAULT_CHAT_AGENT_MODE);
2788
3236
  }, [activeSession, preferredMode]);
2789
- useEffect4(() => {
3237
+ useEffect5(() => {
2790
3238
  if (!attachmentNotice)
2791
3239
  return;
2792
3240
  const timeoutId = window.setTimeout(
@@ -3065,29 +3513,29 @@ var useChatComposer = () => {
3065
3513
  };
3066
3514
 
3067
3515
  // src/components/chat-composer/components/chat-composer-attachment-list.tsx
3068
- import { useState as useState7 } from "react";
3069
- import styled11 from "@emotion/styled";
3070
- import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs8 } from "@emotion/react/jsx-runtime";
3516
+ import { useState as useState8 } from "react";
3517
+ import styled10 from "@emotion/styled";
3518
+ import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs8 } from "@emotion/react/jsx-runtime";
3071
3519
  var ChatComposerAttachmentList = ({
3072
3520
  attachments,
3073
3521
  onRemoveAttachment
3074
3522
  }) => {
3075
- const [activeImage, setActiveImage] = useState7(null);
3523
+ const [activeImage, setActiveImage] = useState8(null);
3076
3524
  if (!attachments.length) {
3077
3525
  return null;
3078
3526
  }
3079
3527
  return /* @__PURE__ */ jsxs8(Fragment3, { children: [
3080
- /* @__PURE__ */ jsx12(AttachmentList, { "data-testid": "chat-composer-attachment-list", children: attachments.map((attachment) => /* @__PURE__ */ jsxs8(AttachmentCard, { children: [
3081
- /* @__PURE__ */ jsx12(
3528
+ /* @__PURE__ */ jsx11(AttachmentList, { "data-testid": "chat-composer-attachment-list", children: attachments.map((attachment) => /* @__PURE__ */ jsxs8(AttachmentCard, { children: [
3529
+ /* @__PURE__ */ jsx11(
3082
3530
  AttachmentPreviewButton,
3083
3531
  {
3084
3532
  type: "button",
3085
3533
  "aria-label": `${attachment.name} preview`,
3086
3534
  onClick: () => setActiveImage(attachment),
3087
- children: /* @__PURE__ */ jsx12(AttachmentThumb, { src: attachment.previewUrl, alt: attachment.name })
3535
+ children: /* @__PURE__ */ jsx11(AttachmentThumb, { src: attachment.previewUrl, alt: attachment.name })
3088
3536
  }
3089
3537
  ),
3090
- /* @__PURE__ */ jsx12(
3538
+ /* @__PURE__ */ jsx11(
3091
3539
  AttachmentRemoveButton,
3092
3540
  {
3093
3541
  type: "button",
@@ -3096,11 +3544,11 @@ var ChatComposerAttachmentList = ({
3096
3544
  event.stopPropagation();
3097
3545
  onRemoveAttachment(attachment.id);
3098
3546
  },
3099
- children: /* @__PURE__ */ jsx12(CloseGlyph, { "aria-hidden": "true" })
3547
+ children: /* @__PURE__ */ jsx11(CloseGlyph, { "aria-hidden": "true" })
3100
3548
  }
3101
3549
  )
3102
3550
  ] }, attachment.id)) }),
3103
- activeImage ? /* @__PURE__ */ jsx12(
3551
+ activeImage ? /* @__PURE__ */ jsx11(
3104
3552
  ImageViewer,
3105
3553
  {
3106
3554
  src: activeImage.previewUrl,
@@ -3110,7 +3558,7 @@ var ChatComposerAttachmentList = ({
3110
3558
  ) : null
3111
3559
  ] });
3112
3560
  };
3113
- var AttachmentList = styled11.div`
3561
+ var AttachmentList = styled10.div`
3114
3562
  display: flex;
3115
3563
  flex-wrap: wrap;
3116
3564
  gap: 10px;
@@ -3132,7 +3580,7 @@ var AttachmentList = styled11.div`
3132
3580
  background: transparent;
3133
3581
  }
3134
3582
  `;
3135
- var AttachmentCard = styled11.div`
3583
+ var AttachmentCard = styled10.div`
3136
3584
  position: relative;
3137
3585
  width: 108px;
3138
3586
  height: 72px;
@@ -3141,7 +3589,7 @@ var AttachmentCard = styled11.div`
3141
3589
  border: 1px solid rgba(255, 255, 255, 0.12);
3142
3590
  background: rgba(255, 255, 255, 0.04);
3143
3591
  `;
3144
- var AttachmentPreviewButton = styled11.button`
3592
+ var AttachmentPreviewButton = styled10.button`
3145
3593
  width: 100%;
3146
3594
  height: 100%;
3147
3595
  padding: 0;
@@ -3149,13 +3597,13 @@ var AttachmentPreviewButton = styled11.button`
3149
3597
  background: transparent;
3150
3598
  cursor: zoom-in;
3151
3599
  `;
3152
- var AttachmentThumb = styled11.img`
3600
+ var AttachmentThumb = styled10.img`
3153
3601
  width: 100%;
3154
3602
  height: 100%;
3155
3603
  object-fit: cover;
3156
3604
  display: block;
3157
3605
  `;
3158
- var AttachmentRemoveButton = styled11.button`
3606
+ var AttachmentRemoveButton = styled10.button`
3159
3607
  position: absolute;
3160
3608
  top: 6px;
3161
3609
  right: 6px;
@@ -3193,7 +3641,7 @@ var AttachmentRemoveButton = styled11.button`
3193
3641
  background: rgba(30, 30, 35, 0.98);
3194
3642
  }
3195
3643
  `;
3196
- var CloseGlyph = styled11.span`
3644
+ var CloseGlyph = styled10.span`
3197
3645
  position: relative;
3198
3646
  width: 11px;
3199
3647
  height: 11px;
@@ -3222,9 +3670,9 @@ var CloseGlyph = styled11.span`
3222
3670
  `;
3223
3671
 
3224
3672
  // src/components/chat-composer/components/chat-model-control.tsx
3225
- import styled12 from "@emotion/styled";
3673
+ import styled11 from "@emotion/styled";
3226
3674
  import { Select } from "@xinghunm/compass-ui";
3227
- import { jsx as jsx13, jsxs as jsxs9 } from "@emotion/react/jsx-runtime";
3675
+ import { jsx as jsx12, jsxs as jsxs9 } from "@emotion/react/jsx-runtime";
3228
3676
  var ChatModelControl = ({
3229
3677
  selectedModel,
3230
3678
  availableModels,
@@ -3243,7 +3691,7 @@ var ChatModelControl = ({
3243
3691
  "aria-label": "Reload",
3244
3692
  onClick: onReloadModels,
3245
3693
  children: [
3246
- /* @__PURE__ */ jsx13("span", { children: "Failed to load models" }),
3694
+ /* @__PURE__ */ jsx12("span", { children: "Failed to load models" }),
3247
3695
  /* @__PURE__ */ jsxs9(
3248
3696
  ReloadIcon,
3249
3697
  {
@@ -3255,8 +3703,8 @@ var ChatModelControl = ({
3255
3703
  fill: "currentColor",
3256
3704
  xmlns: "http://www.w3.org/2000/svg",
3257
3705
  children: [
3258
- /* @__PURE__ */ jsx13("path", { d: "M895.469672 511.745197c0-146.498562-82.099856-273.805016-202.788589-338.470805l22.072715-46.630017c-4.50664-12.609179-18.382673-19.176758-30.991852-14.670118l-92.436272 33.040511c-12.609179 4.50664-19.176758 18.382673-14.670118 30.991852l33.040511 92.436272c4.50664 12.609179 18.382673 19.176758 30.991852 14.670118l24.581861-51.92972c99.069343 54.335513 166.240185 159.596881 166.240185 280.561907 0 165.56685-125.817544 301.747415-287.057855 318.14692l0 0.022513c-17.730826 0-32.105209 14.374382-32.105209 32.105209 0 17.730826 14.374382 32.105209 32.105209 32.105209 2.098801 0 4.149507-0.207731 6.135744-0.592494C744.270041 874.039593 895.469672 710.564381 895.469672 511.745197z" }),
3259
- /* @__PURE__ */ jsx13("path", { d: "M480.616222 129.23948c-0.041956 0-0.082888 0.00307-0.124843 0.00307l0-0.00307c-0.01535 0.001023-0.031722 0.00307-0.047072 0.004093-1.892093 0.010233-3.744277 0.189312-5.545296 0.5137-194.674794 18.529005-346.957083 182.459588-346.957083 381.987924 0 147.431817 83.146699 275.42798 205.097168 339.700819l-24.814152 52.419883c4.50664 12.609179 18.382673 19.176758 30.991852 14.670118l92.436272-33.040511c12.609179-4.50664 19.176758-18.382673 14.670118-30.991852l-33.040511-92.436272c-4.50664-12.609179-18.382673-19.176758-30.991852-14.670118l-21.853727 46.167482c-100.326986-53.964052-168.535461-159.920246-168.535461-281.81955 0-166.089759 126.616746-302.591643 288.588721-318.284043l0-0.014326c0.041956 0 0.082888 0.00307 0.124843 0.00307 17.730826 0 32.105209-14.374382 32.105209-32.105209C512.721431 143.613862 498.347049 129.23948 480.616222 129.23948z" })
3706
+ /* @__PURE__ */ jsx12("path", { d: "M895.469672 511.745197c0-146.498562-82.099856-273.805016-202.788589-338.470805l22.072715-46.630017c-4.50664-12.609179-18.382673-19.176758-30.991852-14.670118l-92.436272 33.040511c-12.609179 4.50664-19.176758 18.382673-14.670118 30.991852l33.040511 92.436272c4.50664 12.609179 18.382673 19.176758 30.991852 14.670118l24.581861-51.92972c99.069343 54.335513 166.240185 159.596881 166.240185 280.561907 0 165.56685-125.817544 301.747415-287.057855 318.14692l0 0.022513c-17.730826 0-32.105209 14.374382-32.105209 32.105209 0 17.730826 14.374382 32.105209 32.105209 32.105209 2.098801 0 4.149507-0.207731 6.135744-0.592494C744.270041 874.039593 895.469672 710.564381 895.469672 511.745197z" }),
3707
+ /* @__PURE__ */ jsx12("path", { d: "M480.616222 129.23948c-0.041956 0-0.082888 0.00307-0.124843 0.00307l0-0.00307c-0.01535 0.001023-0.031722 0.00307-0.047072 0.004093-1.892093 0.010233-3.744277 0.189312-5.545296 0.5137-194.674794 18.529005-346.957083 182.459588-346.957083 381.987924 0 147.431817 83.146699 275.42798 205.097168 339.700819l-24.814152 52.419883c4.50664 12.609179 18.382673 19.176758 30.991852 14.670118l92.436272-33.040511c12.609179-4.50664 19.176758-18.382673 14.670118-30.991852l-33.040511-92.436272c-4.50664-12.609179-18.382673-19.176758-30.991852-14.670118l-21.853727 46.167482c-100.326986-53.964052-168.535461-159.920246-168.535461-281.81955 0-166.089759 126.616746-302.591643 288.588721-318.284043l0-0.014326c0.041956 0 0.082888 0.00307 0.124843 0.00307 17.730826 0 32.105209-14.374382 32.105209-32.105209C512.721431 143.613862 498.347049 129.23948 480.616222 129.23948z" })
3260
3708
  ]
3261
3709
  }
3262
3710
  )
@@ -3265,11 +3713,11 @@ var ChatModelControl = ({
3265
3713
  );
3266
3714
  }
3267
3715
  if (isModelsLoading) {
3268
- return /* @__PURE__ */ jsx13(ModelBadge, { children: "Loading models..." });
3716
+ return /* @__PURE__ */ jsx12(ModelBadge, { children: "Loading models..." });
3269
3717
  }
3270
3718
  if (hasModels && selectedModel) {
3271
3719
  if (availableModels.length > 1) {
3272
- return /* @__PURE__ */ jsx13(
3720
+ return /* @__PURE__ */ jsx12(
3273
3721
  ModelSelect,
3274
3722
  {
3275
3723
  "data-testid": "chat-model-select",
@@ -3283,11 +3731,11 @@ var ChatModelControl = ({
3283
3731
  }
3284
3732
  );
3285
3733
  }
3286
- return /* @__PURE__ */ jsx13(ModelBadge, { children: selectedModel });
3734
+ return /* @__PURE__ */ jsx12(ModelBadge, { children: selectedModel });
3287
3735
  }
3288
- return /* @__PURE__ */ jsx13(ModelBadge, { children: "No model available" });
3736
+ return /* @__PURE__ */ jsx12(ModelBadge, { children: "No model available" });
3289
3737
  };
3290
- var ModelBadge = styled12.span`
3738
+ var ModelBadge = styled11.span`
3291
3739
  border-radius: 999px;
3292
3740
  border: 1px solid var(--border-hover);
3293
3741
  padding: 5px 12px;
@@ -3296,7 +3744,7 @@ var ModelBadge = styled12.span`
3296
3744
  color: var(--text-secondary);
3297
3745
  line-height: 12px;
3298
3746
  `;
3299
- var ModelReloadButton = styled12.button`
3747
+ var ModelReloadButton = styled11.button`
3300
3748
  display: inline-flex;
3301
3749
  align-items: center;
3302
3750
  gap: 8px;
@@ -3321,10 +3769,10 @@ var ModelReloadButton = styled12.button`
3321
3769
  color: rgba(255, 255, 255, 0.88);
3322
3770
  }
3323
3771
  `;
3324
- var ReloadIcon = styled12.svg`
3772
+ var ReloadIcon = styled11.svg`
3325
3773
  flex-shrink: 0;
3326
3774
  `;
3327
- var ModelSelect = styled12(Select)`
3775
+ var ModelSelect = styled11(Select)`
3328
3776
  && {
3329
3777
  width: auto;
3330
3778
  min-width: 0;
@@ -3344,16 +3792,16 @@ var ModelSelect = styled12(Select)`
3344
3792
  `;
3345
3793
 
3346
3794
  // src/components/chat-composer/components/chat-mode-control.tsx
3347
- import styled13 from "@emotion/styled";
3795
+ import styled12 from "@emotion/styled";
3348
3796
  import { Select as Select2 } from "@xinghunm/compass-ui";
3349
- import { jsx as jsx14 } from "@emotion/react/jsx-runtime";
3797
+ import { jsx as jsx13 } from "@emotion/react/jsx-runtime";
3350
3798
  var ChatModeControl = ({
3351
3799
  value,
3352
3800
  disabled = false,
3353
3801
  labels,
3354
3802
  onChange
3355
3803
  }) => {
3356
- return /* @__PURE__ */ jsx14(
3804
+ return /* @__PURE__ */ jsx13(
3357
3805
  ModeSelect,
3358
3806
  {
3359
3807
  "data-testid": "chat-mode-select",
@@ -3368,7 +3816,7 @@ var ChatModeControl = ({
3368
3816
  }
3369
3817
  );
3370
3818
  };
3371
- var ModeSelect = styled13(Select2)`
3819
+ var ModeSelect = styled12(Select2)`
3372
3820
  && {
3373
3821
  flex: 0 1 auto;
3374
3822
  width: auto;
@@ -3389,10 +3837,10 @@ var ModeSelect = styled13(Select2)`
3389
3837
  `;
3390
3838
 
3391
3839
  // src/components/chat-composer/components/chat-send-actions.tsx
3392
- import styled14 from "@emotion/styled";
3840
+ import styled13 from "@emotion/styled";
3393
3841
  import { Button } from "@xinghunm/compass-ui";
3394
- import { Fragment as Fragment4, jsx as jsx15 } from "@emotion/react/jsx-runtime";
3395
- var ArrowUpIcon = () => /* @__PURE__ */ jsx15(
3842
+ import { Fragment as Fragment4, jsx as jsx14 } from "@emotion/react/jsx-runtime";
3843
+ var ArrowUpIcon = () => /* @__PURE__ */ jsx14(
3396
3844
  "svg",
3397
3845
  {
3398
3846
  "aria-hidden": "true",
@@ -3401,7 +3849,7 @@ var ArrowUpIcon = () => /* @__PURE__ */ jsx15(
3401
3849
  viewBox: "0 0 12 12",
3402
3850
  fill: "none",
3403
3851
  xmlns: "http://www.w3.org/2000/svg",
3404
- children: /* @__PURE__ */ jsx15(
3852
+ children: /* @__PURE__ */ jsx14(
3405
3853
  "path",
3406
3854
  {
3407
3855
  d: "M6 10V2M6 2L2 6M6 2L10 6",
@@ -3419,7 +3867,7 @@ var ChatSendActions = ({
3419
3867
  isStopping,
3420
3868
  onStop,
3421
3869
  onSend
3422
- }) => /* @__PURE__ */ jsx15(Fragment4, { children: isStreaming ? /* @__PURE__ */ jsx15(
3870
+ }) => /* @__PURE__ */ jsx14(Fragment4, { children: isStreaming ? /* @__PURE__ */ jsx14(
3423
3871
  StopButton,
3424
3872
  {
3425
3873
  type: "button",
@@ -3429,14 +3877,14 @@ var ChatSendActions = ({
3429
3877
  disabled: isStopping,
3430
3878
  shape: "circle",
3431
3879
  onClick: () => void onStop(),
3432
- children: /* @__PURE__ */ jsx15(StopGlyph, { "aria-hidden": "true" })
3880
+ children: isStopping ? /* @__PURE__ */ jsx14(StopSpinner, { "aria-hidden": "true", "data-testid": "chat-composer-stop-spinner" }) : /* @__PURE__ */ jsx14(StopGlyph, { "aria-hidden": "true", "data-testid": "chat-composer-stop-glyph" })
3433
3881
  }
3434
- ) : /* @__PURE__ */ jsx15(
3882
+ ) : /* @__PURE__ */ jsx14(
3435
3883
  PrimaryButton,
3436
3884
  {
3437
3885
  $canSend: canSend,
3438
3886
  type: "button",
3439
- icon: /* @__PURE__ */ jsx15(ArrowUpIcon, {}),
3887
+ icon: /* @__PURE__ */ jsx14(ArrowUpIcon, {}),
3440
3888
  "aria-label": "Send",
3441
3889
  "data-testid": "chat-composer-send",
3442
3890
  disabled: !canSend,
@@ -3444,7 +3892,7 @@ var ChatSendActions = ({
3444
3892
  onClick: () => void onSend()
3445
3893
  }
3446
3894
  ) });
3447
- var PrimaryButton = styled14(Button)`
3895
+ var PrimaryButton = styled13(Button)`
3448
3896
  && {
3449
3897
  min-width: 24px;
3450
3898
  width: 24px;
@@ -3470,7 +3918,7 @@ var PrimaryButton = styled14(Button)`
3470
3918
  }
3471
3919
  }
3472
3920
  `;
3473
- var StopButton = styled14(Button)`
3921
+ var StopButton = styled13(Button)`
3474
3922
  && {
3475
3923
  min-width: 24px;
3476
3924
  width: 24px;
@@ -3498,17 +3946,35 @@ var StopButton = styled14(Button)`
3498
3946
  }
3499
3947
  }
3500
3948
  `;
3501
- var StopGlyph = styled14.span`
3949
+ var StopGlyph = styled13.span`
3502
3950
  width: 8px;
3503
3951
  height: 8px;
3504
3952
  border-radius: 2px;
3505
3953
  background: #1b1b1b;
3506
3954
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);
3507
3955
  `;
3956
+ var StopSpinner = styled13.span`
3957
+ width: 10px;
3958
+ height: 10px;
3959
+ border-radius: 999px;
3960
+ border: 1.5px solid rgba(27, 27, 27, 0.2);
3961
+ border-top-color: #1b1b1b;
3962
+ animation: chat-composer-stop-spin 0.7s linear infinite;
3963
+
3964
+ @keyframes chat-composer-stop-spin {
3965
+ from {
3966
+ transform: rotate(0deg);
3967
+ }
3968
+
3969
+ to {
3970
+ transform: rotate(360deg);
3971
+ }
3972
+ }
3973
+ `;
3508
3974
 
3509
3975
  // src/components/chat-composer/index.tsx
3510
- import { jsx as jsx16, jsxs as jsxs10 } from "@emotion/react/jsx-runtime";
3511
- var PlusIcon = () => /* @__PURE__ */ jsx16(
3976
+ import { jsx as jsx15, jsxs as jsxs10 } from "@emotion/react/jsx-runtime";
3977
+ var PlusIcon = () => /* @__PURE__ */ jsx15(
3512
3978
  "svg",
3513
3979
  {
3514
3980
  "aria-hidden": "true",
@@ -3517,7 +3983,7 @@ var PlusIcon = () => /* @__PURE__ */ jsx16(
3517
3983
  viewBox: "0 0 16 16",
3518
3984
  fill: "none",
3519
3985
  xmlns: "http://www.w3.org/2000/svg",
3520
- children: /* @__PURE__ */ jsx16("path", { d: "M8 3v10M3 8h10", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round" })
3986
+ children: /* @__PURE__ */ jsx15("path", { d: "M8 3v10M3 8h10", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round" })
3521
3987
  }
3522
3988
  );
3523
3989
  var ChatComposerView = ({
@@ -3546,7 +4012,7 @@ var ChatComposerView = ({
3546
4012
  onStop,
3547
4013
  onSend
3548
4014
  }) => {
3549
- const imageInputRef = useRef7(null);
4015
+ const imageInputRef = useRef8(null);
3550
4016
  const canSend = canSendChatMessage({
3551
4017
  value,
3552
4018
  attachmentCount: attachments.length,
@@ -3577,8 +4043,8 @@ var ChatComposerView = ({
3577
4043
  event.preventDefault();
3578
4044
  onPasteImages(imageFiles);
3579
4045
  };
3580
- return /* @__PURE__ */ jsx16(Container2, { children: /* @__PURE__ */ jsxs10(Surface, { "data-testid": "chat-composer-surface", children: [
3581
- enableImageAttachments ? /* @__PURE__ */ jsx16(
4046
+ return /* @__PURE__ */ jsx15(Container2, { children: /* @__PURE__ */ jsxs10(Surface, { "data-testid": "chat-composer-surface", children: [
4047
+ enableImageAttachments ? /* @__PURE__ */ jsx15(
3582
4048
  "input",
3583
4049
  {
3584
4050
  ref: imageInputRef,
@@ -3590,15 +4056,15 @@ var ChatComposerView = ({
3590
4056
  onChange: handlePickImages
3591
4057
  }
3592
4058
  ) : null,
3593
- /* @__PURE__ */ jsx16(
4059
+ /* @__PURE__ */ jsx15(
3594
4060
  ChatComposerAttachmentList,
3595
4061
  {
3596
4062
  attachments,
3597
4063
  onRemoveAttachment
3598
4064
  }
3599
4065
  ),
3600
- attachmentNotice === "limit_reached" ? /* @__PURE__ */ jsx16(AttachmentNotice, { "data-testid": "chat-composer-attachment-notice", children: attachmentLimitNotice }) : null,
3601
- /* @__PURE__ */ jsx16(
4066
+ attachmentNotice === "limit_reached" ? /* @__PURE__ */ jsx15(AttachmentNotice, { "data-testid": "chat-composer-attachment-notice", children: attachmentLimitNotice }) : null,
4067
+ /* @__PURE__ */ jsx15(
3602
4068
  Input,
3603
4069
  {
3604
4070
  "data-testid": "chat-composer-input",
@@ -3609,18 +4075,18 @@ var ChatComposerView = ({
3609
4075
  placeholder
3610
4076
  }
3611
4077
  ),
3612
- /* @__PURE__ */ jsx16(Footer, { children: /* @__PURE__ */ jsxs10(Actions2, { "data-testid": "chat-composer-actions", children: [
3613
- enableImageAttachments ? /* @__PURE__ */ jsx16(
4078
+ /* @__PURE__ */ jsx15(Footer, { children: /* @__PURE__ */ jsxs10(Actions2, { "data-testid": "chat-composer-actions", children: [
4079
+ enableImageAttachments ? /* @__PURE__ */ jsx15(
3614
4080
  AttachButton,
3615
4081
  {
3616
4082
  type: "button",
3617
4083
  "data-testid": "chat-composer-attach-image",
3618
4084
  "aria-label": "Attach image",
3619
4085
  onClick: () => imageInputRef.current?.click(),
3620
- children: /* @__PURE__ */ jsx16(PlusIcon, {})
4086
+ children: /* @__PURE__ */ jsx15(PlusIcon, {})
3621
4087
  }
3622
4088
  ) : null,
3623
- /* @__PURE__ */ jsx16(
4089
+ /* @__PURE__ */ jsx15(
3624
4090
  ChatModeControl,
3625
4091
  {
3626
4092
  value: selectedMode,
@@ -3629,7 +4095,7 @@ var ChatComposerView = ({
3629
4095
  onChange: onSelectedModeChange
3630
4096
  }
3631
4097
  ),
3632
- /* @__PURE__ */ jsx16(
4098
+ /* @__PURE__ */ jsx15(
3633
4099
  ChatModelControl,
3634
4100
  {
3635
4101
  selectedModel,
@@ -3641,7 +4107,7 @@ var ChatComposerView = ({
3641
4107
  onReloadModels
3642
4108
  }
3643
4109
  ),
3644
- /* @__PURE__ */ jsx16(
4110
+ /* @__PURE__ */ jsx15(
3645
4111
  ChatSendActions,
3646
4112
  {
3647
4113
  canSend,
@@ -3658,7 +4124,7 @@ var ChatComposer = () => {
3658
4124
  const { labels, sendRef, retryRef, enableImageAttachments } = useChatContext();
3659
4125
  const { state, actions } = useChatComposer();
3660
4126
  const { send, retry } = actions;
3661
- useEffect5(() => {
4127
+ useEffect6(() => {
3662
4128
  sendRef.current = send;
3663
4129
  retryRef.current = async () => {
3664
4130
  retry();
@@ -3669,7 +4135,7 @@ var ChatComposer = () => {
3669
4135
  plan: labels.modeLabelPlan,
3670
4136
  agent: labels.modeLabelAgent
3671
4137
  };
3672
- return /* @__PURE__ */ jsx16(
4138
+ return /* @__PURE__ */ jsx15(
3673
4139
  ChatComposerView,
3674
4140
  {
3675
4141
  value: state.value,
@@ -3699,10 +4165,10 @@ var ChatComposer = () => {
3699
4165
  }
3700
4166
  );
3701
4167
  };
3702
- var Container2 = styled15.div`
4168
+ var Container2 = styled14.div`
3703
4169
  padding: 0 16px 16px;
3704
4170
  `;
3705
- var Surface = styled15.div`
4171
+ var Surface = styled14.div`
3706
4172
  background: var(--border-color);
3707
4173
  border-radius: 20px;
3708
4174
  border: 1px solid var(--border-hover);
@@ -3711,7 +4177,7 @@ var Surface = styled15.div`
3711
4177
  0 12px 36px rgba(0, 0, 0, 0.3);
3712
4178
  backdrop-filter: blur(10px);
3713
4179
  `;
3714
- var AttachmentNotice = styled15.div`
4180
+ var AttachmentNotice = styled14.div`
3715
4181
  margin: 10px 12px 0;
3716
4182
  padding: 8px 10px;
3717
4183
  border-radius: 10px;
@@ -3721,7 +4187,7 @@ var AttachmentNotice = styled15.div`
3721
4187
  font-size: 12px;
3722
4188
  line-height: 1.4;
3723
4189
  `;
3724
- var Input = styled15.textarea`
4190
+ var Input = styled14.textarea`
3725
4191
  width: 100%;
3726
4192
  min-height: 96px;
3727
4193
  resize: none;
@@ -3743,14 +4209,14 @@ var Input = styled15.textarea`
3743
4209
  display: none;
3744
4210
  }
3745
4211
  `;
3746
- var Footer = styled15.div`
4212
+ var Footer = styled14.div`
3747
4213
  display: flex;
3748
4214
  align-items: flex-end;
3749
4215
  justify-content: stretch;
3750
4216
  gap: 16px;
3751
4217
  padding: 0 14px 14px;
3752
4218
  `;
3753
- var Actions2 = styled15.div`
4219
+ var Actions2 = styled14.div`
3754
4220
  display: flex;
3755
4221
  align-items: center;
3756
4222
  flex-wrap: wrap;
@@ -3759,7 +4225,7 @@ var Actions2 = styled15.div`
3759
4225
  justify-content: flex-end;
3760
4226
  gap: 8px;
3761
4227
  `;
3762
- var AttachButton = styled15.button`
4228
+ var AttachButton = styled14.button`
3763
4229
  width: 28px;
3764
4230
  height: 28px;
3765
4231
  display: grid;
@@ -3776,42 +4242,42 @@ var AttachButton = styled15.button`
3776
4242
  `;
3777
4243
 
3778
4244
  // src/components/chat-conversation-list/index.tsx
3779
- import styled17 from "@emotion/styled";
4245
+ import styled16 from "@emotion/styled";
3780
4246
 
3781
4247
  // src/components/chat-conversation-list/components/chat-session-item.tsx
3782
- import { memo as memo3 } from "react";
3783
- import styled16 from "@emotion/styled";
3784
- import { jsx as jsx17, jsxs as jsxs11 } from "@emotion/react/jsx-runtime";
3785
- var ChatSessionItem = memo3(
4248
+ import { memo as memo2 } from "react";
4249
+ import styled15 from "@emotion/styled";
4250
+ import { jsx as jsx16, jsxs as jsxs11 } from "@emotion/react/jsx-runtime";
4251
+ var ChatSessionItem = memo2(
3786
4252
  ({ session, isActive, modeLabel, onClick }) => {
3787
- return /* @__PURE__ */ jsx17(
4253
+ return /* @__PURE__ */ jsx16(
3788
4254
  SessionButton,
3789
4255
  {
3790
4256
  type: "button",
3791
4257
  "data-active": isActive,
3792
4258
  onClick: () => onClick(session.sessionId),
3793
4259
  children: /* @__PURE__ */ jsxs11(SessionMeta, { children: [
3794
- /* @__PURE__ */ jsx17(SessionTitle, { children: session.title }),
3795
- /* @__PURE__ */ jsx17(ModeBadge, { children: modeLabel })
4260
+ /* @__PURE__ */ jsx16(SessionTitle, { children: session.title }),
4261
+ /* @__PURE__ */ jsx16(ModeBadge, { children: modeLabel })
3796
4262
  ] })
3797
4263
  }
3798
4264
  );
3799
4265
  }
3800
4266
  );
3801
4267
  ChatSessionItem.displayName = "ChatSessionItem";
3802
- var SessionMeta = styled16.div`
4268
+ var SessionMeta = styled15.div`
3803
4269
  display: flex;
3804
4270
  align-items: center;
3805
4271
  justify-content: space-between;
3806
4272
  gap: 8px;
3807
4273
  `;
3808
- var SessionTitle = styled16.span`
4274
+ var SessionTitle = styled15.span`
3809
4275
  min-width: 0;
3810
4276
  overflow: hidden;
3811
4277
  text-overflow: ellipsis;
3812
4278
  white-space: nowrap;
3813
4279
  `;
3814
- var SessionButton = styled16.button`
4280
+ var SessionButton = styled15.button`
3815
4281
  border: 1px solid transparent;
3816
4282
  border-radius: 12px;
3817
4283
  padding: 12px;
@@ -3825,7 +4291,7 @@ var SessionButton = styled16.button`
3825
4291
  background: rgba(255, 255, 255, 0.08);
3826
4292
  }
3827
4293
  `;
3828
- var ModeBadge = styled16.span`
4294
+ var ModeBadge = styled15.span`
3829
4295
  flex-shrink: 0;
3830
4296
  border-radius: 999px;
3831
4297
  border: 1px solid rgba(255, 255, 255, 0.1);
@@ -3837,7 +4303,7 @@ var ModeBadge = styled16.span`
3837
4303
  `;
3838
4304
 
3839
4305
  // src/components/chat-conversation-list/index.tsx
3840
- import { jsx as jsx18, jsxs as jsxs12 } from "@emotion/react/jsx-runtime";
4306
+ import { jsx as jsx17, jsxs as jsxs12 } from "@emotion/react/jsx-runtime";
3841
4307
  var ChatConversationList = () => {
3842
4308
  const { labels } = useChatContext();
3843
4309
  const sessions = useChatStore((s) => s.sessions);
@@ -3862,10 +4328,10 @@ var ChatConversationList = () => {
3862
4328
  };
3863
4329
  return /* @__PURE__ */ jsxs12(Container3, { children: [
3864
4330
  /* @__PURE__ */ jsxs12(Toolbar, { children: [
3865
- /* @__PURE__ */ jsx18(Title4, { children: "Sessions" }),
3866
- /* @__PURE__ */ jsx18(CreateButton, { type: "button", "data-testid": "chat-create-session", onClick: handleCreateSession, children: labels.newChat })
4331
+ /* @__PURE__ */ jsx17(Title4, { children: "Sessions" }),
4332
+ /* @__PURE__ */ jsx17(CreateButton, { type: "button", "data-testid": "chat-create-session", onClick: handleCreateSession, children: labels.newChat })
3867
4333
  ] }),
3868
- /* @__PURE__ */ jsx18(List2, { "data-testid": "chat-session-list", children: sessions.map((session) => /* @__PURE__ */ jsx18(
4334
+ /* @__PURE__ */ jsx17(List2, { "data-testid": "chat-session-list", children: sessions.map((session) => /* @__PURE__ */ jsx17(
3869
4335
  ChatSessionItem,
3870
4336
  {
3871
4337
  session,
@@ -3877,7 +4343,7 @@ var ChatConversationList = () => {
3877
4343
  )) })
3878
4344
  ] });
3879
4345
  };
3880
- var Container3 = styled17.aside`
4346
+ var Container3 = styled16.aside`
3881
4347
  width: 280px;
3882
4348
  min-width: 280px;
3883
4349
  border-right: 1px solid var(--border-default, rgba(255, 255, 255, 0.08));
@@ -3885,18 +4351,18 @@ var Container3 = styled17.aside`
3885
4351
  flex-direction: column;
3886
4352
  background: rgba(255, 255, 255, 0.02);
3887
4353
  `;
3888
- var Toolbar = styled17.div`
4354
+ var Toolbar = styled16.div`
3889
4355
  padding: 20px 16px 12px;
3890
4356
  display: flex;
3891
4357
  flex-direction: column;
3892
4358
  gap: 12px;
3893
4359
  `;
3894
- var Title4 = styled17.h2`
4360
+ var Title4 = styled16.h2`
3895
4361
  margin: 0;
3896
4362
  font-size: 14px;
3897
4363
  color: var(--text-secondary);
3898
4364
  `;
3899
- var CreateButton = styled17.button`
4365
+ var CreateButton = styled16.button`
3900
4366
  border: none;
3901
4367
  border-radius: 12px;
3902
4368
  padding: 12px 14px;
@@ -3905,7 +4371,7 @@ var CreateButton = styled17.button`
3905
4371
  text-align: left;
3906
4372
  cursor: pointer;
3907
4373
  `;
3908
- var List2 = styled17.div`
4374
+ var List2 = styled16.div`
3909
4375
  padding: 0 12px 16px;
3910
4376
  display: flex;
3911
4377
  flex-direction: column;
@@ -3914,8 +4380,8 @@ var List2 = styled17.div`
3914
4380
  `;
3915
4381
 
3916
4382
  // src/components/ai-chat/index.tsx
3917
- import { jsx as jsx19, jsxs as jsxs13 } from "@emotion/react/jsx-runtime";
3918
- var AiChat = ({ showConversationList = false, ...providerProps }) => /* @__PURE__ */ jsx19(
4383
+ import { jsx as jsx18, jsxs as jsxs13 } from "@emotion/react/jsx-runtime";
4384
+ var AiChat = ({ showConversationList = false, ...providerProps }) => /* @__PURE__ */ jsx18(
3919
4385
  ConfigProvider,
3920
4386
  {
3921
4387
  theme: {
@@ -3950,23 +4416,23 @@ var AiChat = ({ showConversationList = false, ...providerProps }) => /* @__PURE_
3950
4416
  }
3951
4417
  }
3952
4418
  },
3953
- children: /* @__PURE__ */ jsx19(AiChatProvider, { ...providerProps, children: /* @__PURE__ */ jsxs13(Root, { "data-testid": "ai-chat", children: [
3954
- showConversationList ? /* @__PURE__ */ jsx19(ChatConversationList, {}) : null,
4419
+ children: /* @__PURE__ */ jsx18(AiChatProvider, { ...providerProps, children: /* @__PURE__ */ jsxs13(Root, { "data-testid": "ai-chat", children: [
4420
+ showConversationList ? /* @__PURE__ */ jsx18(ChatConversationList, {}) : null,
3955
4421
  /* @__PURE__ */ jsxs13(Workspace, { children: [
3956
- /* @__PURE__ */ jsx19(ChatThread, {}),
3957
- /* @__PURE__ */ jsx19(ChatComposer, {})
4422
+ /* @__PURE__ */ jsx18(ChatThread, {}),
4423
+ /* @__PURE__ */ jsx18(ChatComposer, {})
3958
4424
  ] })
3959
4425
  ] }) })
3960
4426
  }
3961
4427
  );
3962
- var Root = styled18.div`
4428
+ var Root = styled17.div`
3963
4429
  display: flex;
3964
4430
  width: 100%;
3965
4431
  height: 100%;
3966
4432
  min-height: 0;
3967
4433
  overflow: hidden;
3968
4434
  `;
3969
- var Workspace = styled18.section`
4435
+ var Workspace = styled17.section`
3970
4436
  flex: 1;
3971
4437
  display: flex;
3972
4438
  flex-direction: column;
@@ -3977,6 +4443,7 @@ export {
3977
4443
  AiChat,
3978
4444
  AiChatProvider,
3979
4445
  CHAT_AGENT_MODES,
4446
+ CHAT_MESSAGE_RENDER_ORDERS,
3980
4447
  ChatComposer,
3981
4448
  ChatConversationList,
3982
4449
  ChatThread,