@xinghunm/ai-chat 0.2.2 → 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
@@ -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,7 +600,7 @@ 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";
603
+ import { useCallback as useCallback2, useLayoutEffect, useMemo as useMemo4, useRef as useRef5, useState as useState5 } from "react";
594
604
  import styled9 from "@emotion/styled";
595
605
 
596
606
  // src/context/use-chat-context.ts
@@ -611,7 +621,7 @@ var useChatStore = (selector) => {
611
621
  var CHAT_THREAD_SCROLL_TOP_GAP = 16;
612
622
 
613
623
  // src/components/chat-thread/components/chat-message-item.tsx
614
- import { Fragment, memo, useState as useState3 } from "react";
624
+ import { Fragment, memo, useState as useState4 } from "react";
615
625
  import styled7 from "@emotion/styled";
616
626
  import { keyframes } from "@emotion/react";
617
627
  import ReactMarkdown from "react-markdown";
@@ -851,6 +861,7 @@ var useChatMessageReveal = (message) => {
851
861
  ];
852
862
  return {
853
863
  isAssistantStreaming,
864
+ isFreshBlockActive,
854
865
  displayedContent,
855
866
  settledContent,
856
867
  freshContent,
@@ -858,6 +869,303 @@ var useChatMessageReveal = (message) => {
858
869
  };
859
870
  };
860
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
1166
+ };
1167
+ };
1168
+
861
1169
  // src/components/chat-thread/components/pde-ai-execution-confirmation-card.tsx
862
1170
  import styled from "@emotion/styled";
863
1171
  import { jsx as jsx2, jsxs } from "@emotion/react/jsx-runtime";
@@ -1068,7 +1376,7 @@ var Value = styled3.span`
1068
1376
  `;
1069
1377
 
1070
1378
  // src/components/chat-thread/components/pde-ai-questionnaire-card.tsx
1071
- import { useState as useState2 } from "react";
1379
+ import { useState as useState3 } from "react";
1072
1380
  import styled4 from "@emotion/styled";
1073
1381
  import { jsx as jsx5, jsxs as jsxs3 } from "@emotion/react/jsx-runtime";
1074
1382
  var OTHER_OPTION_VALUE = "__other__";
@@ -1170,10 +1478,10 @@ var PDEAIQuestionnaireCardInner = ({
1170
1478
  interactive = false,
1171
1479
  onSubmit
1172
1480
  }) => {
1173
- const [answers, setAnswers] = useState2(
1481
+ const [answers, setAnswers] = useState3(
1174
1482
  () => createInitialAnswers(questionnaire)
1175
1483
  );
1176
- const [errorMessage, setErrorMessage] = useState2(null);
1484
+ const [errorMessage, setErrorMessage] = useState3(null);
1177
1485
  const handleSubmit = () => {
1178
1486
  const missingQuestions = questionnaire.questions.filter(
1179
1487
  (question) => question.required && isMissingRequiredAnswer(question, answers)
@@ -1609,7 +1917,7 @@ var Detail = styled5.li`
1609
1917
 
1610
1918
  // src/components/chat-thread/components/image-viewer.tsx
1611
1919
  import styled6 from "@emotion/styled";
1612
- import { useEffect as useEffect2, useRef as useRef3 } from "react";
1920
+ import { useEffect as useEffect3, useRef as useRef4 } from "react";
1613
1921
  import { jsx as jsx7 } from "@emotion/react/jsx-runtime";
1614
1922
  var Overlay = styled6.div`
1615
1923
  position: fixed;
@@ -1628,8 +1936,8 @@ var Img = styled6.img`
1628
1936
  border-radius: 4px;
1629
1937
  `;
1630
1938
  var ImageViewer = ({ src, alt, onClose }) => {
1631
- const overlayRef = useRef3(null);
1632
- useEffect2(() => {
1939
+ const overlayRef = useRef4(null);
1940
+ useEffect3(() => {
1633
1941
  const handleKey = (e) => {
1634
1942
  if (e.key === "Escape")
1635
1943
  onClose();
@@ -1637,7 +1945,7 @@ var ImageViewer = ({ src, alt, onClose }) => {
1637
1945
  document.addEventListener("keydown", handleKey);
1638
1946
  return () => document.removeEventListener("keydown", handleKey);
1639
1947
  }, [onClose]);
1640
- useEffect2(() => {
1948
+ useEffect3(() => {
1641
1949
  overlayRef.current?.focus();
1642
1950
  }, []);
1643
1951
  const stopPropagation = (e) => e.stopPropagation();
@@ -1800,9 +2108,16 @@ var ChatMessageItemView = ({
1800
2108
  onQuestionnaireSubmit,
1801
2109
  renderMessageBlock
1802
2110
  }) => {
1803
- const { labels } = useChatContext();
1804
- const [activeImage, setActiveImage] = useState3(void 0);
1805
- const { displayedBlocks, 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);
1806
2121
  const isStoppedAssistant = message.role === "assistant" && message.status === "stopped";
1807
2122
  const attachments = message.attachments ?? [];
1808
2123
  const blocks = message.blocks ?? [];
@@ -1814,6 +2129,22 @@ var ChatMessageItemView = ({
1814
2129
  const canSubmitConfirmation = isPlanMode && typeof onConfirmationSubmit === "function";
1815
2130
  const canSubmitQuestionnaire = isPlanMode && typeof onQuestionnaireSubmit === "function";
1816
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
+ });
1817
2148
  const renderChatMessageBlock = (block, index) => {
1818
2149
  switch (block.type) {
1819
2150
  case "markdown":
@@ -1870,19 +2201,69 @@ var ChatMessageItemView = ({
1870
2201
  return null;
1871
2202
  }
1872
2203
  };
1873
- const renderTextContent = () => /* @__PURE__ */ jsxs5(Fragment2, { children: [
1874
- displayedBlocks.filter((block) => block.content).map((block, index) => /* @__PURE__ */ jsx8(
1875
- ContentBlock,
1876
- {
1877
- "data-testid": block.tone === "fresh" ? "chat-message-fresh-block" : "chat-message-settled-block",
1878
- "data-block-tone": block.tone,
1879
- "data-block-index": index,
1880
- children: renderMarkdownContent(block.content)
1881
- },
1882
- `${block.tone}-${index}`
1883
- )),
1884
- !displayedBlocks.some((block) => block.content) && !settledContent && !freshContent && hasTextContent ? /* @__PURE__ */ jsx8(ContentBlock, { "data-testid": "chat-message-settled-block", "data-block-tone": "settled", children: renderMarkdownContent(displayedContent) }) : null
1885
- ] });
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
+ })();
1886
2267
  return /* @__PURE__ */ jsxs5(Fragment2, { children: [
1887
2268
  /* @__PURE__ */ jsxs5(Bubble, { "data-role": message.role, "data-status": message.status ?? "done", children: [
1888
2269
  /* @__PURE__ */ jsxs5(Header2, { children: [
@@ -1901,17 +2282,18 @@ var ChatMessageItemView = ({
1901
2282
  isStoppedAssistant ? /* @__PURE__ */ jsx8(StatusTag, { "data-testid": "chat-message-stopped-tag", children: labels.stoppedResponse }) : null
1902
2283
  ] }),
1903
2284
  /* @__PURE__ */ jsxs5(Content, { "data-testid": "chat-message-content", children: [
1904
- shouldRenderStructuredBlocks || hasTextContent ? /* @__PURE__ */ jsxs5(ContentStack, { "data-testid": "chat-message-body-stack", children: [
1905
- shouldRenderStructuredBlocks ? blocks.map((block, index) => /* @__PURE__ */ jsx8(
1906
- ContentSegment,
1907
- {
1908
- "data-testid": "chat-message-content-segment",
1909
- children: renderChatMessageBlock(block, index)
1910
- },
1911
- `${block.type}-${index}`
1912
- )) : null,
1913
- hasTextContent ? /* @__PURE__ */ jsx8(ContentSegment, { "data-testid": "chat-message-content-segment", children: renderTextContent() }) : null
1914
- ] }) : 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,
1915
2297
  attachments.length ? /* @__PURE__ */ jsx8(AttachmentGrid, { "data-testid": "chat-message-attachment-grid", children: attachments.map((attachment) => /* @__PURE__ */ jsx8(
1916
2298
  AttachmentButton,
1917
2299
  {
@@ -2303,17 +2685,17 @@ var ChatThreadView = ({
2303
2685
  onQuestionnaireSubmit,
2304
2686
  renderMessageBlock
2305
2687
  }) => {
2306
- const containerRef = useRef4(null);
2307
- const conversationTurns = useMemo3(
2688
+ const containerRef = useRef5(null);
2689
+ const conversationTurns = useMemo4(
2308
2690
  () => groupConversationTurns(historyMessages, streamingMessage),
2309
2691
  [historyMessages, streamingMessage]
2310
2692
  );
2311
2693
  const latestTurn = conversationTurns[conversationTurns.length - 1];
2312
2694
  const previousTurns = conversationTurns.slice(0, -1);
2313
2695
  const latestUserMessageId = latestTurn?.userMessage?.id;
2314
- const latestUserMessageRef = useRef4(null);
2315
- const reservedSpaceFrameRef = useRef4(null);
2316
- const [latestTurnMinHeight, setLatestTurnMinHeight] = useState4(0);
2696
+ const latestUserMessageRef = useRef5(null);
2697
+ const reservedSpaceFrameRef = useRef5(null);
2698
+ const [latestTurnMinHeight, setLatestTurnMinHeight] = useState5(0);
2317
2699
  const measureLatestTurnMinHeight = useCallback2(() => {
2318
2700
  const container = containerRef.current;
2319
2701
  if (!container)
@@ -2566,7 +2948,7 @@ var RetryButton = styled9.button`
2566
2948
  `;
2567
2949
 
2568
2950
  // src/components/chat-composer/index.tsx
2569
- import { useEffect as useEffect5, useRef as useRef7 } from "react";
2951
+ import { useEffect as useEffect6, useRef as useRef8 } from "react";
2570
2952
  import styled14 from "@emotion/styled";
2571
2953
 
2572
2954
  // src/components/chat-composer/lib/chat-composer.ts
@@ -2678,10 +3060,10 @@ var resolveSendSession = ({
2678
3060
  };
2679
3061
 
2680
3062
  // src/components/chat-composer/hooks/use-chat-composer.ts
2681
- 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";
2682
3064
 
2683
3065
  // src/components/chat-composer/hooks/use-composer-attachments.ts
2684
- 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";
2685
3067
  var SUPPORTED_IMAGE_MIME_TYPES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/webp"]);
2686
3068
  var MAX_COMPOSER_ATTACHMENTS = 10;
2687
3069
  var createObjectUrl = (file) => typeof URL !== "undefined" && typeof URL.createObjectURL === "function" ? URL.createObjectURL(file) : "";
@@ -2695,12 +3077,12 @@ var releaseComposerAttachments = (attachments) => {
2695
3077
  attachments.forEach((attachment) => revokeObjectUrl(attachment.previewUrl));
2696
3078
  };
2697
3079
  var useComposerAttachments = () => {
2698
- const [attachments, setAttachments] = useState5([]);
2699
- const attachmentsRef = useRef5([]);
2700
- useEffect3(() => {
3080
+ const [attachments, setAttachments] = useState6([]);
3081
+ const attachmentsRef = useRef6([]);
3082
+ useEffect4(() => {
2701
3083
  attachmentsRef.current = attachments;
2702
3084
  }, [attachments]);
2703
- useEffect3(
3085
+ useEffect4(
2704
3086
  () => () => {
2705
3087
  releaseComposerAttachments(attachmentsRef.current);
2706
3088
  },
@@ -2813,9 +3195,9 @@ var useChatComposer = () => {
2813
3195
  const clearSessionError = useChatStore((s) => s.clearSessionError);
2814
3196
  const setPreferredMode = useChatStore((s) => s.setPreferredMode);
2815
3197
  const setSessionMode = useChatStore((s) => s.setSessionMode);
2816
- const [availableModels, setAvailableModels] = useState6([]);
2817
- const [isModelsLoading, setIsModelsLoading] = useState6(true);
2818
- const [isModelsError, setIsModelsError] = useState6(false);
3198
+ const [availableModels, setAvailableModels] = useState7([]);
3199
+ const [isModelsLoading, setIsModelsLoading] = useState7(true);
3200
+ const [isModelsError, setIsModelsError] = useState7(false);
2819
3201
  const fetchModels = useCallback3(async () => {
2820
3202
  setIsModelsLoading(true);
2821
3203
  setIsModelsError(false);
@@ -2828,31 +3210,31 @@ var useChatComposer = () => {
2828
3210
  setIsModelsLoading(false);
2829
3211
  }
2830
3212
  }, [transport]);
2831
- useEffect4(() => {
3213
+ useEffect5(() => {
2832
3214
  void fetchModels();
2833
3215
  }, [fetchModels]);
2834
3216
  const hasModels = availableModels.length > 0;
2835
- const [value, setValue] = useState6("");
2836
- const [selectedModel, setSelectedModel] = useState6("");
2837
- const [selectedMode, setSelectedModeLocal] = useState6(DEFAULT_CHAT_AGENT_MODE);
2838
- 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);
2839
3221
  const { attachments, appendFiles, removeAttachment, takeMessageAttachments } = useComposerAttachments();
2840
- const abortControllerRef = useRef6(null);
2841
- const stopRequestRef = useRef6(null);
2842
- const lastRequestRef = useRef6(null);
2843
- useEffect4(() => {
3222
+ const abortControllerRef = useRef7(null);
3223
+ const stopRequestRef = useRef7(null);
3224
+ const lastRequestRef = useRef7(null);
3225
+ useEffect5(() => {
2844
3226
  setSelectedModel(
2845
3227
  (current) => resolveSelectedChatModel({ currentModel: current, availableModels, isModelsLoading })
2846
3228
  );
2847
3229
  }, [availableModels, isModelsLoading]);
2848
- useEffect4(() => {
3230
+ useEffect5(() => {
2849
3231
  if (activeSession) {
2850
3232
  setSelectedModeLocal(activeSession.mode ?? DEFAULT_CHAT_AGENT_MODE);
2851
3233
  return;
2852
3234
  }
2853
3235
  setSelectedModeLocal(preferredMode ?? DEFAULT_CHAT_AGENT_MODE);
2854
3236
  }, [activeSession, preferredMode]);
2855
- useEffect4(() => {
3237
+ useEffect5(() => {
2856
3238
  if (!attachmentNotice)
2857
3239
  return;
2858
3240
  const timeoutId = window.setTimeout(
@@ -3131,14 +3513,14 @@ var useChatComposer = () => {
3131
3513
  };
3132
3514
 
3133
3515
  // src/components/chat-composer/components/chat-composer-attachment-list.tsx
3134
- import { useState as useState7 } from "react";
3516
+ import { useState as useState8 } from "react";
3135
3517
  import styled10 from "@emotion/styled";
3136
3518
  import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs8 } from "@emotion/react/jsx-runtime";
3137
3519
  var ChatComposerAttachmentList = ({
3138
3520
  attachments,
3139
3521
  onRemoveAttachment
3140
3522
  }) => {
3141
- const [activeImage, setActiveImage] = useState7(null);
3523
+ const [activeImage, setActiveImage] = useState8(null);
3142
3524
  if (!attachments.length) {
3143
3525
  return null;
3144
3526
  }
@@ -3630,7 +4012,7 @@ var ChatComposerView = ({
3630
4012
  onStop,
3631
4013
  onSend
3632
4014
  }) => {
3633
- const imageInputRef = useRef7(null);
4015
+ const imageInputRef = useRef8(null);
3634
4016
  const canSend = canSendChatMessage({
3635
4017
  value,
3636
4018
  attachmentCount: attachments.length,
@@ -3742,7 +4124,7 @@ var ChatComposer = () => {
3742
4124
  const { labels, sendRef, retryRef, enableImageAttachments } = useChatContext();
3743
4125
  const { state, actions } = useChatComposer();
3744
4126
  const { send, retry } = actions;
3745
- useEffect5(() => {
4127
+ useEffect6(() => {
3746
4128
  sendRef.current = send;
3747
4129
  retryRef.current = async () => {
3748
4130
  retry();
@@ -4061,6 +4443,7 @@ export {
4061
4443
  AiChat,
4062
4444
  AiChatProvider,
4063
4445
  CHAT_AGENT_MODES,
4446
+ CHAT_MESSAGE_RENDER_ORDERS,
4064
4447
  ChatComposer,
4065
4448
  ChatConversationList,
4066
4449
  ChatThread,