@xinghunm/ai-chat 0.3.0 → 1.0.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
@@ -31,10 +31,18 @@ var DEFAULT_AI_CHAT_LABELS = {
31
31
  attachmentLimitNotice: "Images exceeded the limit. Only the first 10 images were kept.",
32
32
  userRoleLabel: "User",
33
33
  assistantRoleLabel: "Assistant",
34
+ expandMessageAriaLabel: "Expand message",
35
+ collapseMessageAriaLabel: "Collapse message",
36
+ expandComposerAriaLabel: "Expand composer",
37
+ collapseComposerAriaLabel: "Collapse composer",
34
38
  stoppedResponse: "Response stopped",
35
39
  assistantStreamingAriaLabel: "assistant streaming",
36
40
  networkError: "Network request failed. Please try again.",
37
- genericError: "Something went wrong. Please try again."
41
+ genericError: "Something went wrong. Please try again.",
42
+ questionnaireSubmitting: "Submitting...",
43
+ questionnaireSubmitted: "Selection submitted. Waiting for the plan to continue...",
44
+ questionnaireValidationPrefix: "Please complete:",
45
+ questionnaireSubmitFailed: "Failed to submit. Please try again."
38
46
  };
39
47
 
40
48
  // src/store/chat-store.ts
@@ -50,6 +58,24 @@ var resolveSessionTitleFromMessage = (message) => {
50
58
  }
51
59
  return DEFAULT_CHAT_SESSION_TITLE;
52
60
  };
61
+ var mergeStreamingBlocks = (existingBlocks, incomingBlocks) => {
62
+ const nextBlocks = [...existingBlocks ?? []];
63
+ incomingBlocks.forEach((incomingBlock) => {
64
+ if (incomingBlock.type !== "questionnaire") {
65
+ nextBlocks.push(incomingBlock);
66
+ return;
67
+ }
68
+ const existingIndex = nextBlocks.findIndex(
69
+ (block) => block.type === "questionnaire" && block.questionnaire.questionnaireId === incomingBlock.questionnaire.questionnaireId
70
+ );
71
+ if (existingIndex === -1) {
72
+ nextBlocks.push(incomingBlock);
73
+ return;
74
+ }
75
+ nextBlocks[existingIndex] = incomingBlock;
76
+ });
77
+ return nextBlocks;
78
+ };
53
79
  var finalizeStreamingMessage = (state, sessionId, status, clearError = false) => {
54
80
  const message = state.streamingMessageBySession[sessionId];
55
81
  const hasRenderableMessagePayload = Boolean(
@@ -239,10 +265,15 @@ var createChatStore = (initialState) => createStore((set, get) => ({
239
265
  const target = state.streamingMessageBySession[sessionId];
240
266
  if (!target)
241
267
  return;
268
+ const nextBlocks = patch.blocks !== void 0 ? mergeStreamingBlocks(target.blocks, patch.blocks) : target.blocks;
242
269
  set({
243
270
  streamingMessageBySession: {
244
271
  ...state.streamingMessageBySession,
245
- [sessionId]: { ...target, ...patch }
272
+ [sessionId]: {
273
+ ...target,
274
+ ...patch,
275
+ ...patch.blocks !== void 0 ? { blocks: nextBlocks } : {}
276
+ }
246
277
  }
247
278
  });
248
279
  },
@@ -462,9 +493,27 @@ var DEFAULT_CHAT_TRANSPORT_ENDPOINTS = {
462
493
  completions: "/chat/completions",
463
494
  terminate: "/chat/terminate"
464
495
  };
496
+ var createToolExecutionHeaders = (policy) => {
497
+ if (!policy?.enabled) {
498
+ return {};
499
+ }
500
+ return {
501
+ "X-Tool-Approval-Required": String(Boolean(policy.approvalRequired)),
502
+ ...typeof policy.approvalTimeoutSec === "number" ? { "X-Tool-Approval-Timeout": String(policy.approvalTimeoutSec) } : {}
503
+ };
504
+ };
505
+ var createModeDefaultHeaders = (mode) => {
506
+ if (mode === "ask" || mode === "plan") {
507
+ return {
508
+ "X-Tool-Approval-Required": "false"
509
+ };
510
+ }
511
+ return {};
512
+ };
465
513
  var createDefaultChatTransport = ({
466
514
  apiBaseUrl,
467
515
  authToken,
516
+ toolExecutionPolicy,
468
517
  streamHeaders,
469
518
  transformStreamPacket,
470
519
  endpoints,
@@ -475,6 +524,10 @@ var createDefaultChatTransport = ({
475
524
  ...DEFAULT_CHAT_TRANSPORT_ENDPOINTS,
476
525
  ...endpoints
477
526
  };
527
+ const resolvedStreamHeaders = {
528
+ ...createToolExecutionHeaders(toolExecutionPolicy),
529
+ ...streamHeaders
530
+ };
478
531
  return {
479
532
  getModels: () => getChatModels(client, resolvedEndpoints.models),
480
533
  startStream: async ({
@@ -488,12 +541,16 @@ var createDefaultChatTransport = ({
488
541
  onDone,
489
542
  onError
490
543
  }) => {
544
+ const requestHeaders = {
545
+ ...createModeDefaultHeaders(mode),
546
+ ...resolvedStreamHeaders
547
+ };
491
548
  await startChatStream({
492
549
  apiBaseUrl,
493
550
  endpointPath: resolvedEndpoints.completions,
494
551
  sessionId,
495
552
  authToken,
496
- requestHeaders: streamHeaders,
553
+ requestHeaders,
497
554
  model,
498
555
  mode,
499
556
  content,
@@ -522,6 +579,8 @@ var AiChatProvider = (props) => {
522
579
  defaultMode,
523
580
  labels,
524
581
  renderMessageBlock,
582
+ handleQuestionnaireSubmit,
583
+ handleConfirmationSubmit,
525
584
  messageRenderOrder,
526
585
  enableImageAttachments = true,
527
586
  children
@@ -577,6 +636,8 @@ var AiChatProvider = (props) => {
577
636
  sendRef,
578
637
  retryRef,
579
638
  renderMessageBlock,
639
+ handleQuestionnaireSubmit,
640
+ handleConfirmationSubmit,
580
641
  messageRenderOrder,
581
642
  transformStreamPacket: defaultTransformStreamPacket,
582
643
  enableImageAttachments
@@ -587,6 +648,8 @@ var AiChatProvider = (props) => {
587
648
  defaultAuthToken,
588
649
  defaultTransformStreamPacket,
589
650
  enableImageAttachments,
651
+ handleConfirmationSubmit,
652
+ handleQuestionnaireSubmit,
590
653
  labels,
591
654
  messageRenderOrder,
592
655
  renderMessageBlock,
@@ -600,7 +663,7 @@ var AiChatProvider = (props) => {
600
663
  };
601
664
 
602
665
  // src/components/chat-thread/index.tsx
603
- import { useCallback as useCallback2, useLayoutEffect, useMemo as useMemo4, useRef as useRef5, useState as useState5 } from "react";
666
+ import { useCallback as useCallback3, useLayoutEffect as useLayoutEffect2, useMemo as useMemo4, useRef as useRef4, useState as useState4 } from "react";
604
667
  import styled9 from "@emotion/styled";
605
668
 
606
669
  // src/context/use-chat-context.ts
@@ -621,7 +684,7 @@ var useChatStore = (selector) => {
621
684
  var CHAT_THREAD_SCROLL_TOP_GAP = 16;
622
685
 
623
686
  // src/components/chat-thread/components/chat-message-item.tsx
624
- import { Fragment, memo, useState as useState4 } from "react";
687
+ import { Fragment, memo, useCallback as useCallback2, useLayoutEffect, useState as useState3 } from "react";
625
688
  import styled7 from "@emotion/styled";
626
689
  import { keyframes } from "@emotion/react";
627
690
  import ReactMarkdown from "react-markdown";
@@ -870,7 +933,7 @@ var useChatMessageReveal = (message) => {
870
933
  };
871
934
 
872
935
  // 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";
936
+ import { useEffect as useEffect2, useMemo as useMemo3, useReducer as useReducer2 } from "react";
874
937
 
875
938
  // src/components/chat-thread/lib/chat-message-timeline.ts
876
939
  var stringifyTimelineKeyPart = (value) => {
@@ -896,7 +959,7 @@ var getTimelineBlockKey = (block, index) => {
896
959
  case "confirmation_card":
897
960
  return `${index}:confirmation_card:${block.proposal.proposalId}`;
898
961
  case "result_summary":
899
- return `${index}:result_summary:${block.summary.taskId}:${block.summary.status}`;
962
+ return `${index}:result_summary:${block.summary.summaryId}:${block.summary.status}`;
900
963
  case "questionnaire":
901
964
  return `${index}:questionnaire:${block.questionnaire.questionnaireId}`;
902
965
  case "custom":
@@ -1022,6 +1085,83 @@ var buildAnchoredTimelineSegments = ({
1022
1085
  };
1023
1086
 
1024
1087
  // src/components/chat-thread/hooks/use-timeline-block-anchors.ts
1088
+ var createTimelineAnchorState = ({
1089
+ messageId,
1090
+ currentBlockKeys
1091
+ }) => ({
1092
+ messageId,
1093
+ previousBlockKeys: currentBlockKeys,
1094
+ timelineBlockAnchors: {},
1095
+ visibleTimelineBlockKeys: {}
1096
+ });
1097
+ var timelineAnchorReducer = (state, action) => {
1098
+ switch (action.type) {
1099
+ case "reset-message":
1100
+ if (state.messageId === action.messageId) {
1101
+ return state;
1102
+ }
1103
+ return createTimelineAnchorState(action);
1104
+ case "sync-anchors": {
1105
+ const previousBlockKeys = new Set(state.previousBlockKeys);
1106
+ const nextAnchors = action.currentBlockKeys.reduce(
1107
+ (acc, blockKey) => {
1108
+ const existingAnchor = state.timelineBlockAnchors[blockKey];
1109
+ if (existingAnchor !== void 0) {
1110
+ acc[blockKey] = existingAnchor;
1111
+ return acc;
1112
+ }
1113
+ if (!previousBlockKeys.has(blockKey)) {
1114
+ acc[blockKey] = action.timelineTextStreamLength;
1115
+ }
1116
+ return acc;
1117
+ },
1118
+ {}
1119
+ );
1120
+ const hasAnchorChanged = Object.keys(nextAnchors).length !== Object.keys(state.timelineBlockAnchors).length || Object.entries(nextAnchors).some(
1121
+ ([blockKey, anchor]) => state.timelineBlockAnchors[blockKey] !== anchor
1122
+ );
1123
+ const hasPreviousKeysChanged = action.currentBlockKeys.length !== state.previousBlockKeys.length || action.currentBlockKeys.some(
1124
+ (blockKey, index) => state.previousBlockKeys[index] !== blockKey
1125
+ );
1126
+ if (!hasAnchorChanged && !hasPreviousKeysChanged) {
1127
+ return state;
1128
+ }
1129
+ return {
1130
+ ...state,
1131
+ previousBlockKeys: action.currentBlockKeys,
1132
+ timelineBlockAnchors: hasAnchorChanged ? nextAnchors : state.timelineBlockAnchors
1133
+ };
1134
+ }
1135
+ case "sync-visible": {
1136
+ const nextVisibleBlockKeys = action.currentBlockKeys.reduce(
1137
+ (acc, blockKey) => {
1138
+ if (state.visibleTimelineBlockKeys[blockKey]) {
1139
+ acc[blockKey] = true;
1140
+ return acc;
1141
+ }
1142
+ const anchor = action.effectiveTimelineBlockAnchors[blockKey];
1143
+ if (anchor !== void 0 && anchor <= action.displayedTimelineTextLength) {
1144
+ acc[blockKey] = true;
1145
+ }
1146
+ return acc;
1147
+ },
1148
+ {}
1149
+ );
1150
+ const hasVisibleBlockChanged = Object.keys(nextVisibleBlockKeys).length !== Object.keys(state.visibleTimelineBlockKeys).length || Object.keys(nextVisibleBlockKeys).some(
1151
+ (blockKey) => !state.visibleTimelineBlockKeys[blockKey]
1152
+ );
1153
+ if (!hasVisibleBlockChanged) {
1154
+ return state;
1155
+ }
1156
+ return {
1157
+ ...state,
1158
+ visibleTimelineBlockKeys: nextVisibleBlockKeys
1159
+ };
1160
+ }
1161
+ default:
1162
+ return state;
1163
+ }
1164
+ };
1025
1165
  var useTimelineBlockAnchors = ({
1026
1166
  blocks,
1027
1167
  displayedTimelineTextLength,
@@ -1029,8 +1169,6 @@ var useTimelineBlockAnchors = ({
1029
1169
  message,
1030
1170
  messageRenderOrder
1031
1171
  }) => {
1032
- const [timelineBlockAnchors, setTimelineBlockAnchors] = useState2({});
1033
- const [visibleTimelineBlockKeys, setVisibleTimelineBlockKeys] = useState2({});
1034
1172
  const currentTimelineBlockKeys = useMemo3(
1035
1173
  () => blocks.map((block, index) => getTimelineBlockKey(block, index)).filter((blockKey) => Boolean(blockKey)),
1036
1174
  [blocks]
@@ -1039,20 +1177,22 @@ var useTimelineBlockAnchors = ({
1039
1177
  () => getTimelineDisplayUnitCount(getTimelineTextStream(message.content, blocks)),
1040
1178
  [blocks, message.content]
1041
1179
  );
1042
- const previousTimelineStateRef = useRef3({
1043
- messageId: message.id,
1044
- blockKeys: currentTimelineBlockKeys,
1045
- textLength: timelineTextStreamLength
1046
- });
1180
+ const [state, dispatch] = useReducer2(
1181
+ timelineAnchorReducer,
1182
+ {
1183
+ messageId: message.id,
1184
+ currentBlockKeys: currentTimelineBlockKeys
1185
+ },
1186
+ createTimelineAnchorState
1187
+ );
1047
1188
  const effectiveTimelineBlockAnchors = useMemo3(() => {
1048
1189
  if (messageRenderOrder !== "timeline" || !isAssistantStreaming) {
1049
- return timelineBlockAnchors;
1190
+ return state.timelineBlockAnchors;
1050
1191
  }
1051
- const previousTimelineState = previousTimelineStateRef.current;
1052
- const previousBlockKeys = new Set(previousTimelineState.blockKeys);
1192
+ const previousBlockKeys = new Set(state.previousBlockKeys);
1053
1193
  return currentTimelineBlockKeys.reduce(
1054
1194
  (acc, blockKey) => {
1055
- const existingAnchor = timelineBlockAnchors[blockKey];
1195
+ const existingAnchor = state.timelineBlockAnchors[blockKey];
1056
1196
  if (existingAnchor !== void 0) {
1057
1197
  acc[blockKey] = existingAnchor;
1058
1198
  return acc;
@@ -1062,125 +1202,70 @@ var useTimelineBlockAnchors = ({
1062
1202
  }
1063
1203
  return acc;
1064
1204
  },
1065
- { ...timelineBlockAnchors }
1205
+ { ...state.timelineBlockAnchors }
1066
1206
  );
1067
1207
  }, [
1068
1208
  currentTimelineBlockKeys,
1069
1209
  isAssistantStreaming,
1070
1210
  messageRenderOrder,
1071
- timelineBlockAnchors,
1211
+ state.previousBlockKeys,
1212
+ state.timelineBlockAnchors,
1072
1213
  timelineTextStreamLength
1073
1214
  ]);
1074
1215
  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
- };
1216
+ dispatch({
1217
+ type: "reset-message",
1218
+ messageId: message.id,
1219
+ currentBlockKeys: currentTimelineBlockKeys
1220
+ });
1221
+ }, [currentTimelineBlockKeys, message.id]);
1222
+ useEffect2(() => {
1223
+ if (messageRenderOrder !== "timeline" || !isAssistantStreaming) {
1088
1224
  return;
1089
1225
  }
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
- ]);
1226
+ dispatch({
1227
+ type: "sync-anchors",
1228
+ currentBlockKeys: currentTimelineBlockKeys,
1229
+ timelineTextStreamLength
1230
+ });
1231
+ }, [currentTimelineBlockKeys, isAssistantStreaming, messageRenderOrder, timelineTextStreamLength]);
1130
1232
  useEffect2(() => {
1131
1233
  if (messageRenderOrder !== "timeline") {
1132
- if (Object.keys(visibleTimelineBlockKeys).length > 0) {
1133
- setVisibleTimelineBlockKeys({});
1134
- }
1135
1234
  return;
1136
1235
  }
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
- }
1236
+ dispatch({
1237
+ type: "sync-visible",
1238
+ currentBlockKeys: currentTimelineBlockKeys,
1239
+ effectiveTimelineBlockAnchors,
1240
+ displayedTimelineTextLength
1241
+ });
1155
1242
  }, [
1156
1243
  currentTimelineBlockKeys,
1157
1244
  displayedTimelineTextLength,
1158
1245
  effectiveTimelineBlockAnchors,
1159
- messageRenderOrder,
1160
- timelineBlockAnchors,
1161
- visibleTimelineBlockKeys
1246
+ messageRenderOrder
1162
1247
  ]);
1163
1248
  return {
1164
- timelineBlockAnchors: effectiveTimelineBlockAnchors,
1165
- visibleTimelineBlockKeys
1249
+ timelineBlockAnchors: messageRenderOrder === "timeline" ? effectiveTimelineBlockAnchors : {},
1250
+ visibleTimelineBlockKeys: messageRenderOrder === "timeline" ? state.visibleTimelineBlockKeys : {}
1166
1251
  };
1167
1252
  };
1168
1253
 
1169
- // src/components/chat-thread/components/pde-ai-execution-confirmation-card.tsx
1254
+ // src/components/chat-thread/components/execution-confirmation-card.tsx
1170
1255
  import styled from "@emotion/styled";
1171
1256
  import { jsx as jsx2, jsxs } from "@emotion/react/jsx-runtime";
1172
- var PDEAIExecutionConfirmationCard = ({
1257
+ var ExecutionConfirmationCard = ({
1173
1258
  proposal,
1174
1259
  interactive = false,
1175
1260
  onApply,
1176
1261
  onConfirm,
1177
1262
  onRevise
1178
1263
  }) => {
1179
- return /* @__PURE__ */ jsxs(Card, { "data-testid": "pde-ai-execution-confirmation-card", children: [
1264
+ return /* @__PURE__ */ jsxs(Card, { "data-testid": "execution-confirmation-card", children: [
1180
1265
  /* @__PURE__ */ jsxs(Header, { children: [
1181
1266
  /* @__PURE__ */ jsx2(Eyebrow, { children: "Execution Proposal" }),
1182
- /* @__PURE__ */ jsx2(Title, { children: proposal.equationName }),
1183
- proposal.solverName ? /* @__PURE__ */ jsx2(Subtitle, { children: proposal.solverName }) : null
1267
+ /* @__PURE__ */ jsx2(Title, { children: proposal.resourceName }),
1268
+ proposal.executorName ? /* @__PURE__ */ jsx2(Subtitle, { children: proposal.executorName }) : null
1184
1269
  ] }),
1185
1270
  /* @__PURE__ */ jsx2(SummaryList, { children: proposal.parameterSummary.map((item) => /* @__PURE__ */ jsxs(SummaryItem, { children: [
1186
1271
  /* @__PURE__ */ jsx2(SummaryLabel, { children: item.label }),
@@ -1188,21 +1273,13 @@ var PDEAIExecutionConfirmationCard = ({
1188
1273
  ] }, `${item.fieldPath ?? item.label}-${item.value}`)) }),
1189
1274
  proposal.warnings?.length ? /* @__PURE__ */ jsx2(Warnings, { children: proposal.warnings.map((warning) => /* @__PURE__ */ jsx2(Warning, { children: warning }, warning)) }) : null,
1190
1275
  interactive ? /* @__PURE__ */ jsxs(Actions, { children: [
1191
- onApply && /* @__PURE__ */ jsx2(ActionButton, { type: "button", "data-testid": "pde-ai-confirmation-apply", onClick: onApply, children: "Apply to Parameters" }),
1192
- onConfirm && /* @__PURE__ */ jsx2(
1193
- ActionButton,
1194
- {
1195
- type: "button",
1196
- "data-testid": "pde-ai-confirmation-confirm",
1197
- onClick: onConfirm,
1198
- children: "Confirm Execution"
1199
- }
1200
- ),
1276
+ onApply && /* @__PURE__ */ jsx2(ActionButton, { type: "button", "data-testid": "confirmation-apply", onClick: onApply, children: "Apply to Parameters" }),
1277
+ onConfirm && /* @__PURE__ */ jsx2(ActionButton, { type: "button", "data-testid": "confirmation-confirm", onClick: onConfirm, children: "Confirm Execution" }),
1201
1278
  onRevise && /* @__PURE__ */ jsx2(
1202
1279
  SecondaryActionButton,
1203
1280
  {
1204
1281
  type: "button",
1205
- "data-testid": "pde-ai-confirmation-revise",
1282
+ "data-testid": "confirmation-revise",
1206
1283
  onClick: onRevise,
1207
1284
  children: "Revise Plan"
1208
1285
  }
@@ -1299,10 +1376,10 @@ var SecondaryActionButton = styled(ActionButton)`
1299
1376
  border: 1px solid rgba(255, 255, 255, 0.14);
1300
1377
  `;
1301
1378
 
1302
- // src/components/chat-thread/components/pde-ai-notice-card.tsx
1379
+ // src/components/chat-thread/components/notice-card.tsx
1303
1380
  import styled2 from "@emotion/styled";
1304
1381
  import { jsx as jsx3 } from "@emotion/react/jsx-runtime";
1305
- var PDEAINoticeCard = ({ text, tone }) => /* @__PURE__ */ jsx3(Card2, { "data-testid": "pde-ai-notice-card", "data-tone": tone, children: text });
1382
+ var NoticeCard = ({ text, tone }) => /* @__PURE__ */ jsx3(Card2, { "data-testid": "notice-card", "data-tone": tone, children: text });
1306
1383
  var Card2 = styled2.div`
1307
1384
  padding: 12px 14px;
1308
1385
  border-radius: 16px;
@@ -1328,10 +1405,10 @@ var Card2 = styled2.div`
1328
1405
  }
1329
1406
  `;
1330
1407
 
1331
- // src/components/chat-thread/components/pde-ai-parameter-summary-card.tsx
1408
+ // src/components/chat-thread/components/parameter-summary-card.tsx
1332
1409
  import styled3 from "@emotion/styled";
1333
1410
  import { jsx as jsx4, jsxs as jsxs2 } from "@emotion/react/jsx-runtime";
1334
- var PDEAIParameterSummaryCard = ({ items }) => /* @__PURE__ */ jsxs2(Card3, { "data-testid": "pde-ai-parameter-summary-card", children: [
1411
+ var ParameterSummaryCard = ({ items }) => /* @__PURE__ */ jsxs2(Card3, { "data-testid": "parameter-summary-card", children: [
1335
1412
  /* @__PURE__ */ jsx4(Title2, { children: "Parameter Summary" }),
1336
1413
  /* @__PURE__ */ jsx4(List, { children: items.map((item) => /* @__PURE__ */ jsxs2(ListItem, { children: [
1337
1414
  /* @__PURE__ */ jsx4(Label, { children: item.label }),
@@ -1375,11 +1452,17 @@ var Value = styled3.span`
1375
1452
  text-align: right;
1376
1453
  `;
1377
1454
 
1378
- // src/components/chat-thread/components/pde-ai-questionnaire-card.tsx
1379
- import { useState as useState3 } from "react";
1455
+ // src/components/chat-thread/components/questionnaire-card.tsx
1456
+ import { useState as useState2 } from "react";
1380
1457
  import styled4 from "@emotion/styled";
1381
1458
  import { jsx as jsx5, jsxs as jsxs3 } from "@emotion/react/jsx-runtime";
1382
1459
  var OTHER_OPTION_VALUE = "__other__";
1460
+ var DEFAULT_QUESTIONNAIRE_CARD_LABELS = {
1461
+ submitting: "Submitting...",
1462
+ submitted: "Selection submitted. Waiting for the plan to continue...",
1463
+ validationPrefix: "Please complete:",
1464
+ submitFailed: "Failed to submit. Please try again."
1465
+ };
1383
1466
  var createInitialAnswers = (questionnaire) => ({
1384
1467
  ...questionnaire.answers ?? {}
1385
1468
  });
@@ -1473,26 +1556,40 @@ var normalizeQuestionAnswer = (question, answer) => {
1473
1556
  return answer;
1474
1557
  }
1475
1558
  };
1476
- var PDEAIQuestionnaireCardInner = ({
1559
+ var QuestionnaireCardInner = ({
1477
1560
  questionnaire,
1478
1561
  interactive = false,
1479
- onSubmit
1562
+ onSubmit,
1563
+ labels
1480
1564
  }) => {
1481
- const [answers, setAnswers] = useState3(
1565
+ const [answers, setAnswers] = useState2(
1482
1566
  () => createInitialAnswers(questionnaire)
1483
1567
  );
1484
- const [errorMessage, setErrorMessage] = useState3(null);
1485
- const handleSubmit = () => {
1568
+ const [errorMessage, setErrorMessage] = useState2(null);
1569
+ const [isSubmitting, setIsSubmitting] = useState2(false);
1570
+ const [isSubmitted, setIsSubmitted] = useState2(false);
1571
+ const resolvedLabels = {
1572
+ ...DEFAULT_QUESTIONNAIRE_CARD_LABELS,
1573
+ ...labels
1574
+ };
1575
+ const hasExternalFailureStatus = questionnaire.status === "expired" || questionnaire.status === "failed";
1576
+ const visibleErrorMessage = questionnaire.statusMessage ?? errorMessage;
1577
+ const isInteractionLocked = !interactive || isSubmitting || isSubmitted || hasExternalFailureStatus;
1578
+ const handleSubmit = async () => {
1579
+ if (isSubmitting || isSubmitted) {
1580
+ return;
1581
+ }
1486
1582
  const missingQuestions = questionnaire.questions.filter(
1487
1583
  (question) => question.required && isMissingRequiredAnswer(question, answers)
1488
1584
  );
1489
1585
  if (missingQuestions.length > 0) {
1490
1586
  setErrorMessage(
1491
- `Please complete: ${missingQuestions.map((question) => question.label).join(", ")}`
1587
+ `${resolvedLabels.validationPrefix} ${missingQuestions.map((question) => question.label).join(", ")}`
1492
1588
  );
1493
1589
  return;
1494
1590
  }
1495
1591
  setErrorMessage(null);
1592
+ setIsSubmitting(true);
1496
1593
  const normalizedAnswers = Object.fromEntries(
1497
1594
  questionnaire.questions.flatMap((question) => {
1498
1595
  const value = normalizeQuestionAnswer(question, answers[question.id]);
@@ -1509,11 +1606,18 @@ var PDEAIQuestionnaireCardInner = ({
1509
1606
  return [`- ${question.label}: ${formatQuestionAnswer(question, value)}`];
1510
1607
  })
1511
1608
  ];
1512
- onSubmit?.({
1513
- questionnaireId: questionnaire.questionnaireId,
1514
- answers: normalizedAnswers,
1515
- content: contentLines.join("\n")
1516
- });
1609
+ try {
1610
+ await onSubmit?.({
1611
+ questionnaireId: questionnaire.questionnaireId,
1612
+ answers: normalizedAnswers,
1613
+ content: contentLines.join("\n")
1614
+ });
1615
+ setIsSubmitted(true);
1616
+ } catch (error) {
1617
+ setErrorMessage(error instanceof Error ? error.message : resolvedLabels.submitFailed);
1618
+ } finally {
1619
+ setIsSubmitting(false);
1620
+ }
1517
1621
  };
1518
1622
  const renderQuestion = (question) => {
1519
1623
  const renderOptionChoice = ({
@@ -1528,13 +1632,13 @@ var PDEAIQuestionnaireCardInner = ({
1528
1632
  OptionChoiceItem,
1529
1633
  {
1530
1634
  role: "button",
1531
- tabIndex: interactive ? 0 : -1,
1635
+ tabIndex: isInteractionLocked ? -1 : 0,
1532
1636
  "aria-pressed": isSelected,
1533
1637
  "data-selected": isSelected,
1534
1638
  "data-tone": tone,
1535
- "data-testid": `pde-ai-question-option-${questionId}-${index}`,
1639
+ "data-testid": `question-option-${questionId}-${index}`,
1536
1640
  onClick: (event) => {
1537
- if (!interactive) {
1641
+ if (isInteractionLocked) {
1538
1642
  return;
1539
1643
  }
1540
1644
  if (event.target instanceof HTMLElement && event.target.closest("input")) {
@@ -1543,7 +1647,7 @@ var PDEAIQuestionnaireCardInner = ({
1543
1647
  onClick();
1544
1648
  },
1545
1649
  onKeyDown: (event) => {
1546
- if (!interactive) {
1650
+ if (isInteractionLocked) {
1547
1651
  return;
1548
1652
  }
1549
1653
  if (event.key !== "Enter" && event.key !== " ") {
@@ -1602,11 +1706,11 @@ var PDEAIQuestionnaireCardInner = ({
1602
1706
  inlineInput: singleSelectDraft.selectedValue === OTHER_OPTION_VALUE ? /* @__PURE__ */ jsx5(
1603
1707
  InlineOtherInput,
1604
1708
  {
1605
- "data-testid": `pde-ai-question-input-${question.id}`,
1709
+ "data-testid": `question-input-${question.id}`,
1606
1710
  type: "text",
1607
1711
  value: singleSelectDraft.otherValue,
1608
1712
  placeholder: "Other",
1609
- readOnly: !interactive,
1713
+ readOnly: isInteractionLocked,
1610
1714
  onClick: (event) => {
1611
1715
  event.stopPropagation();
1612
1716
  },
@@ -1627,11 +1731,11 @@ var PDEAIQuestionnaireCardInner = ({
1627
1731
  return /* @__PURE__ */ jsx5(QuestionBody, { children: /* @__PURE__ */ jsx5(
1628
1732
  TextInput,
1629
1733
  {
1630
- "data-testid": `pde-ai-question-input-${question.id}`,
1734
+ "data-testid": `question-input-${question.id}`,
1631
1735
  type: "text",
1632
1736
  value: getTextInputValue(answers[question.id]),
1633
1737
  placeholder: question.placeholder,
1634
- readOnly: !interactive,
1738
+ readOnly: isInteractionLocked,
1635
1739
  onChange: (event) => {
1636
1740
  setAnswers((current) => updateAnswerValue(current, question.id, event.target.value));
1637
1741
  }
@@ -1642,11 +1746,11 @@ var PDEAIQuestionnaireCardInner = ({
1642
1746
  /* @__PURE__ */ jsx5(
1643
1747
  TextInput,
1644
1748
  {
1645
- "data-testid": `pde-ai-question-input-${question.id}`,
1749
+ "data-testid": `question-input-${question.id}`,
1646
1750
  type: "number",
1647
1751
  value: getNumberInputValue(answers[question.id]),
1648
1752
  placeholder: question.placeholder,
1649
- readOnly: !interactive,
1753
+ readOnly: isInteractionLocked,
1650
1754
  onChange: (event) => {
1651
1755
  setAnswers(
1652
1756
  (current) => updateAnswerValue(
@@ -1681,7 +1785,7 @@ var PDEAIQuestionnaireCardInner = ({
1681
1785
  return null;
1682
1786
  }
1683
1787
  };
1684
- return /* @__PURE__ */ jsxs3(Card4, { "data-testid": "pde-ai-questionnaire-card", children: [
1788
+ return /* @__PURE__ */ jsxs3(Card4, { "data-testid": "questionnaire-card", children: [
1685
1789
  questionnaire.title ? /* @__PURE__ */ jsx5(Title3, { children: questionnaire.title }) : null,
1686
1790
  questionnaire.description ? /* @__PURE__ */ jsx5(Description, { children: questionnaire.description }) : null,
1687
1791
  /* @__PURE__ */ jsx5(QuestionList, { children: questionnaire.questions.map((question) => /* @__PURE__ */ jsxs3(QuestionCard, { children: [
@@ -1691,24 +1795,28 @@ var PDEAIQuestionnaireCardInner = ({
1691
1795
  ] }),
1692
1796
  renderQuestion(question)
1693
1797
  ] }, question.id)) }),
1694
- errorMessage ? /* @__PURE__ */ jsx5(ErrorMessage, { "data-testid": "pde-ai-questionnaire-error", children: errorMessage }) : null,
1695
- interactive ? /* @__PURE__ */ jsx5(
1798
+ visibleErrorMessage ? /* @__PURE__ */ jsx5(ErrorMessage, { "data-testid": "questionnaire-error", children: visibleErrorMessage }) : null,
1799
+ isSubmitted ? /* @__PURE__ */ jsx5(SuccessMessage, { "data-testid": "questionnaire-success", children: resolvedLabels.submitted }) : interactive && !hasExternalFailureStatus ? /* @__PURE__ */ jsx5(
1696
1800
  SubmitButton,
1697
1801
  {
1698
1802
  type: "button",
1699
- "data-testid": "pde-ai-questionnaire-submit",
1700
- onClick: handleSubmit,
1701
- children: questionnaire.submitLabel ?? "Submit"
1803
+ "data-testid": "questionnaire-submit",
1804
+ disabled: isInteractionLocked,
1805
+ onClick: () => {
1806
+ void handleSubmit();
1807
+ },
1808
+ children: isSubmitting ? resolvedLabels.submitting : questionnaire.submitLabel ?? "Submit"
1702
1809
  }
1703
1810
  ) : null
1704
1811
  ] });
1705
1812
  };
1706
1813
  var getQuestionnaireStateKey = (questionnaire) => JSON.stringify([
1707
1814
  questionnaire.questionnaireId,
1708
- questionnaire.answers ?? {},
1709
- questionnaire.questions
1815
+ questionnaire.questions,
1816
+ questionnaire.status,
1817
+ questionnaire.statusMessage
1710
1818
  ]);
1711
- var PDEAIQuestionnaireCard = (props) => /* @__PURE__ */ jsx5(PDEAIQuestionnaireCardInner, { ...props }, getQuestionnaireStateKey(props.questionnaire));
1819
+ var QuestionnaireCard = (props) => /* @__PURE__ */ jsx5(QuestionnaireCardInner, { ...props }, getQuestionnaireStateKey(props.questionnaire));
1712
1820
  var Card4 = styled4.section`
1713
1821
  display: grid;
1714
1822
  gap: 14px;
@@ -1856,6 +1964,10 @@ var ErrorMessage = styled4.div`
1856
1964
  color: rgba(255, 145, 145, 0.96);
1857
1965
  font-size: 12px;
1858
1966
  `;
1967
+ var SuccessMessage = styled4.div`
1968
+ color: rgba(164, 255, 210, 0.96);
1969
+ font-size: 12px;
1970
+ `;
1859
1971
  var SubmitButton = styled4.button`
1860
1972
  justify-self: flex-start;
1861
1973
  border: none;
@@ -1866,12 +1978,17 @@ var SubmitButton = styled4.button`
1866
1978
  font-weight: 700;
1867
1979
  padding: 10px 14px;
1868
1980
  cursor: pointer;
1981
+
1982
+ &:disabled {
1983
+ cursor: default;
1984
+ opacity: 0.72;
1985
+ }
1869
1986
  `;
1870
1987
 
1871
- // src/components/chat-thread/components/pde-ai-result-summary-card.tsx
1988
+ // src/components/chat-thread/components/result-summary-card.tsx
1872
1989
  import styled5 from "@emotion/styled";
1873
1990
  import { jsx as jsx6, jsxs as jsxs4 } from "@emotion/react/jsx-runtime";
1874
- var PDEAIResultSummaryCard = ({ summary }) => /* @__PURE__ */ jsxs4(Card5, { "data-testid": "pde-ai-result-summary-card", "data-status": summary.status, children: [
1991
+ var ResultSummaryCard = ({ summary }) => /* @__PURE__ */ jsxs4(Card5, { "data-testid": "result-summary-card", "data-status": summary.status, children: [
1875
1992
  /* @__PURE__ */ jsx6(Eyebrow2, { children: summary.status }),
1876
1993
  /* @__PURE__ */ jsx6(Headline, { children: summary.headline }),
1877
1994
  /* @__PURE__ */ jsx6(Details, { children: summary.details.map((detail) => /* @__PURE__ */ jsx6(Detail, { children: detail }, detail)) })
@@ -1917,7 +2034,7 @@ var Detail = styled5.li`
1917
2034
 
1918
2035
  // src/components/chat-thread/components/image-viewer.tsx
1919
2036
  import styled6 from "@emotion/styled";
1920
- import { useEffect as useEffect3, useRef as useRef4 } from "react";
2037
+ import { useEffect as useEffect3, useRef as useRef3 } from "react";
1921
2038
  import { jsx as jsx7 } from "@emotion/react/jsx-runtime";
1922
2039
  var Overlay = styled6.div`
1923
2040
  position: fixed;
@@ -1936,7 +2053,7 @@ var Img = styled6.img`
1936
2053
  border-radius: 4px;
1937
2054
  `;
1938
2055
  var ImageViewer = ({ src, alt, onClose }) => {
1939
- const overlayRef = useRef4(null);
2056
+ const overlayRef = useRef3(null);
1940
2057
  useEffect3(() => {
1941
2058
  const handleKey = (e) => {
1942
2059
  if (e.key === "Escape")
@@ -1967,6 +2084,7 @@ var ImageViewer = ({ src, alt, onClose }) => {
1967
2084
  import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs5 } from "@emotion/react/jsx-runtime";
1968
2085
  var MARKDOWN_REMARK_PLUGINS = [remarkGfm, remarkMath];
1969
2086
  var MARKDOWN_REHYPE_PLUGINS = [rehypeKatex];
2087
+ var USER_MESSAGE_COLLAPSE_HEIGHT_PX = 120;
1970
2088
  var MARKDOWN_COMPONENTS = {
1971
2089
  table: ({ node: _node, ...props }) => /* @__PURE__ */ jsx8(TableWrapper, { children: /* @__PURE__ */ jsx8("table", { ...props }) })
1972
2090
  };
@@ -1979,10 +2097,101 @@ var renderMarkdownContent = (content) => /* @__PURE__ */ jsx8(
1979
2097
  children: content
1980
2098
  }
1981
2099
  );
2100
+ var renderPlainTextContent = (content) => content;
2101
+ var CollapseIcon = ({ expanded }) => /* @__PURE__ */ jsx8(
2102
+ "svg",
2103
+ {
2104
+ "aria-hidden": "true",
2105
+ width: "16",
2106
+ height: "16",
2107
+ viewBox: "0 0 16 16",
2108
+ fill: "none",
2109
+ xmlns: "http://www.w3.org/2000/svg",
2110
+ children: /* @__PURE__ */ jsx8(
2111
+ "path",
2112
+ {
2113
+ d: "M4 6l4 4 4-4",
2114
+ stroke: "currentColor",
2115
+ strokeWidth: "1.6",
2116
+ strokeLinecap: "round",
2117
+ strokeLinejoin: "round",
2118
+ transform: expanded ? "rotate(180 8 8)" : void 0
2119
+ }
2120
+ )
2121
+ }
2122
+ );
2123
+ var useUserMessageCollapse = ({
2124
+ blocks,
2125
+ displayedBlocks,
2126
+ displayedContent,
2127
+ enabled,
2128
+ freshContent,
2129
+ settledContent
2130
+ }) => {
2131
+ const [isCollapsible, setIsCollapsible] = useState3(false);
2132
+ const [isExpanded, setIsExpanded] = useState3(false);
2133
+ const [bodyStackElement, setBodyStackElement] = useState3(null);
2134
+ const syncCollapseState = useCallback2(
2135
+ (element) => {
2136
+ const nextCollapsible = enabled && (element?.scrollHeight ?? 0) > USER_MESSAGE_COLLAPSE_HEIGHT_PX;
2137
+ setIsCollapsible(nextCollapsible);
2138
+ if (!nextCollapsible) {
2139
+ setIsExpanded(false);
2140
+ }
2141
+ },
2142
+ [enabled]
2143
+ );
2144
+ const bodyStackRef = useCallback2(
2145
+ (node) => {
2146
+ setBodyStackElement(node);
2147
+ syncCollapseState(node);
2148
+ },
2149
+ [syncCollapseState]
2150
+ );
2151
+ useLayoutEffect(() => {
2152
+ if (!bodyStackElement) {
2153
+ return;
2154
+ }
2155
+ const frameId = requestAnimationFrame(() => {
2156
+ syncCollapseState(bodyStackElement);
2157
+ });
2158
+ return () => {
2159
+ cancelAnimationFrame(frameId);
2160
+ };
2161
+ }, [
2162
+ blocks,
2163
+ bodyStackElement,
2164
+ displayedBlocks,
2165
+ displayedContent,
2166
+ enabled,
2167
+ freshContent,
2168
+ settledContent,
2169
+ syncCollapseState
2170
+ ]);
2171
+ useLayoutEffect(() => {
2172
+ if (!bodyStackElement || !enabled || typeof ResizeObserver === "undefined") {
2173
+ return;
2174
+ }
2175
+ const observer = new ResizeObserver(() => {
2176
+ syncCollapseState(bodyStackElement);
2177
+ });
2178
+ observer.observe(bodyStackElement);
2179
+ return () => {
2180
+ observer.disconnect();
2181
+ };
2182
+ }, [bodyStackElement, enabled, syncCollapseState]);
2183
+ return {
2184
+ bodyStackRef,
2185
+ isCollapsed: isCollapsible && !isExpanded,
2186
+ isCollapsible,
2187
+ isExpanded,
2188
+ toggleExpanded: () => setIsExpanded((current) => !current)
2189
+ };
2190
+ };
1982
2191
  var createExecutionConfirmationContent = (proposal) => [
1983
2192
  "Execution confirmed",
1984
- `- Equation: ${proposal.equationName}`,
1985
- ...proposal.solverName ? [`- Solver: ${proposal.solverName}`] : [],
2193
+ `- Equation: ${proposal.resourceName}`,
2194
+ ...proposal.executorName ? [`- Solver: ${proposal.executorName}`] : [],
1986
2195
  `- Proposal ID: ${proposal.proposalId}`
1987
2196
  ].join("\n");
1988
2197
  var areChatAttachmentsEqual = (previousAttachments, nextAttachments) => {
@@ -2004,10 +2213,10 @@ var areParameterSummaryItemsEqual = (previousItems, nextItems) => previousItems.
2004
2213
  const nextItem = nextItems[index];
2005
2214
  return item.label === nextItem?.label && item.value === nextItem.value && item.fieldPath === nextItem.fieldPath;
2006
2215
  });
2007
- var areExecutionProposalsEqual = (previousProposal, nextProposal) => previousProposal.proposalId === nextProposal.proposalId && previousProposal.equationKey === nextProposal.equationKey && previousProposal.equationName === nextProposal.equationName && previousProposal.solverName === nextProposal.solverName && previousProposal.requiresConfirmation === nextProposal.requiresConfirmation && areParameterSummaryItemsEqual(previousProposal.parameterSummary, nextProposal.parameterSummary) && (previousProposal.warnings ?? []).length === (nextProposal.warnings ?? []).length && (previousProposal.warnings ?? []).every(
2216
+ var areExecutionProposalsEqual = (previousProposal, nextProposal) => previousProposal.proposalId === nextProposal.proposalId && previousProposal.resourceKey === nextProposal.resourceKey && previousProposal.resourceName === nextProposal.resourceName && previousProposal.executorName === nextProposal.executorName && previousProposal.requiresConfirmation === nextProposal.requiresConfirmation && areParameterSummaryItemsEqual(previousProposal.parameterSummary, nextProposal.parameterSummary) && (previousProposal.warnings ?? []).length === (nextProposal.warnings ?? []).length && (previousProposal.warnings ?? []).every(
2008
2217
  (warning, index) => warning === nextProposal.warnings?.[index]
2009
2218
  );
2010
- var areResultSummariesEqual = (previousSummary, nextSummary) => previousSummary.taskId === nextSummary.taskId && previousSummary.status === nextSummary.status && previousSummary.headline === nextSummary.headline && previousSummary.details.length === nextSummary.details.length && previousSummary.details.every((detail, index) => detail === nextSummary.details[index]);
2219
+ var areResultSummariesEqual = (previousSummary, nextSummary) => previousSummary.summaryId === nextSummary.summaryId && previousSummary.status === nextSummary.status && previousSummary.headline === nextSummary.headline && previousSummary.details.length === nextSummary.details.length && previousSummary.details.every((detail, index) => detail === nextSummary.details[index]);
2011
2220
  var areStringArraysEqual = (previousValues, nextValues) => previousValues.length === nextValues.length && previousValues.every((value, index) => value === nextValues[index]);
2012
2221
  var areQuestionAnswersEqual = (previousAnswer, nextAnswer) => {
2013
2222
  if (Array.isArray(previousAnswer) || Array.isArray(nextAnswer)) {
@@ -2048,7 +2257,7 @@ var arePlanQuestionsEqual = (previousQuestion, nextQuestion) => {
2048
2257
  return false;
2049
2258
  }
2050
2259
  };
2051
- var areQuestionnairesEqual = (previousQuestionnaire, nextQuestionnaire) => previousQuestionnaire.questionnaireId === nextQuestionnaire.questionnaireId && previousQuestionnaire.title === nextQuestionnaire.title && previousQuestionnaire.description === nextQuestionnaire.description && previousQuestionnaire.submitLabel === nextQuestionnaire.submitLabel && previousQuestionnaire.questions.length === nextQuestionnaire.questions.length && previousQuestionnaire.questions.every(
2260
+ var areQuestionnairesEqual = (previousQuestionnaire, nextQuestionnaire) => previousQuestionnaire.questionnaireId === nextQuestionnaire.questionnaireId && previousQuestionnaire.title === nextQuestionnaire.title && previousQuestionnaire.description === nextQuestionnaire.description && previousQuestionnaire.submitLabel === nextQuestionnaire.submitLabel && previousQuestionnaire.status === nextQuestionnaire.status && previousQuestionnaire.statusMessage === nextQuestionnaire.statusMessage && previousQuestionnaire.questions.length === nextQuestionnaire.questions.length && previousQuestionnaire.questions.every(
2052
2261
  (question, index) => arePlanQuestionsEqual(question, nextQuestionnaire.questions[index])
2053
2262
  ) && areQuestionAnswerMapsEqual(previousQuestionnaire.answers, nextQuestionnaire.answers);
2054
2263
  var areMessageBlocksEqual = (previousBlocks, nextBlocks) => {
@@ -2109,7 +2318,7 @@ var ChatMessageItemView = ({
2109
2318
  renderMessageBlock
2110
2319
  }) => {
2111
2320
  const { labels, messageRenderOrder = "blocks-first" } = useChatContext();
2112
- const [activeImage, setActiveImage] = useState4(void 0);
2321
+ const [activeImage, setActiveImage] = useState3(void 0);
2113
2322
  const {
2114
2323
  displayedBlocks,
2115
2324
  displayedContent,
@@ -2129,6 +2338,8 @@ var ChatMessageItemView = ({
2129
2338
  const canSubmitConfirmation = isPlanMode && typeof onConfirmationSubmit === "function";
2130
2339
  const canSubmitQuestionnaire = isPlanMode && typeof onQuestionnaireSubmit === "function";
2131
2340
  const shouldShowStreamingCaret = isAssistantStreaming && (!shouldRenderStructuredBlocks || hasTextContent);
2341
+ const isUserMessage = message.role === "user";
2342
+ const messageRenderMode = isUserMessage ? "plain-text" : "markdown";
2132
2343
  const timelineConsumedText = messageRenderOrder === "timeline" ? getTimelineConsumedText(blocks) : "";
2133
2344
  const hasConsumedTimelineText = timelineConsumedText.length > 0 && displayedContent.startsWith(timelineConsumedText);
2134
2345
  const timelineDisplayedContent = hasConsumedTimelineText ? displayedContent.slice(timelineConsumedText.length) : displayedContent;
@@ -2145,6 +2356,21 @@ var ChatMessageItemView = ({
2145
2356
  message,
2146
2357
  messageRenderOrder
2147
2358
  });
2359
+ const {
2360
+ bodyStackRef,
2361
+ isCollapsed: isUserMessageCollapsed,
2362
+ isCollapsible: isUserMessageCollapsible,
2363
+ isExpanded: isUserMessageExpanded,
2364
+ toggleExpanded: toggleUserMessageExpanded
2365
+ } = useUserMessageCollapse({
2366
+ blocks,
2367
+ displayedBlocks,
2368
+ displayedContent,
2369
+ enabled: isUserMessage,
2370
+ freshContent,
2371
+ settledContent
2372
+ });
2373
+ const renderMessageContent = (content) => messageRenderMode === "plain-text" ? renderPlainTextContent(content) : renderMarkdownContent(content);
2148
2374
  const renderChatMessageBlock = (block, index) => {
2149
2375
  switch (block.type) {
2150
2376
  case "markdown":
@@ -2153,17 +2379,18 @@ var ChatMessageItemView = ({
2153
2379
  {
2154
2380
  "data-testid": `chat-message-block-${index}`,
2155
2381
  "data-block-tone": "settled",
2156
- children: renderMarkdownContent(block.text)
2382
+ "data-render-mode": messageRenderMode,
2383
+ children: renderMessageContent(block.text)
2157
2384
  },
2158
2385
  `markdown-${index}`
2159
2386
  );
2160
2387
  case "notice":
2161
- return /* @__PURE__ */ jsx8(Fragment, { children: /* @__PURE__ */ jsx8(PDEAINoticeCard, { text: block.text, tone: block.tone }) }, `notice-${index}`);
2388
+ return /* @__PURE__ */ jsx8(Fragment, { children: /* @__PURE__ */ jsx8(NoticeCard, { text: block.text, tone: block.tone }) }, `notice-${index}`);
2162
2389
  case "parameter_summary":
2163
- return /* @__PURE__ */ jsx8(Fragment, { children: /* @__PURE__ */ jsx8(PDEAIParameterSummaryCard, { items: block.items }) }, `parameter-summary-${index}`);
2390
+ return /* @__PURE__ */ jsx8(Fragment, { children: /* @__PURE__ */ jsx8(ParameterSummaryCard, { items: block.items }) }, `parameter-summary-${index}`);
2164
2391
  case "confirmation_card":
2165
2392
  return /* @__PURE__ */ jsx8(Fragment, { children: /* @__PURE__ */ jsx8(
2166
- PDEAIExecutionConfirmationCard,
2393
+ ExecutionConfirmationCard,
2167
2394
  {
2168
2395
  proposal: block.proposal,
2169
2396
  interactive: isPlanMode,
@@ -2175,13 +2402,19 @@ var ChatMessageItemView = ({
2175
2402
  }
2176
2403
  ) }, `confirmation-card-${index}`);
2177
2404
  case "result_summary":
2178
- return /* @__PURE__ */ jsx8(Fragment, { children: /* @__PURE__ */ jsx8(PDEAIResultSummaryCard, { summary: block.summary }) }, `result-summary-${index}`);
2405
+ return /* @__PURE__ */ jsx8(Fragment, { children: /* @__PURE__ */ jsx8(ResultSummaryCard, { summary: block.summary }) }, `result-summary-${index}`);
2179
2406
  case "questionnaire":
2180
2407
  return /* @__PURE__ */ jsx8(Fragment, { children: /* @__PURE__ */ jsx8(
2181
- PDEAIQuestionnaireCard,
2408
+ QuestionnaireCard,
2182
2409
  {
2183
2410
  questionnaire: block.questionnaire,
2184
2411
  interactive: canSubmitQuestionnaire,
2412
+ labels: {
2413
+ submitting: labels.questionnaireSubmitting,
2414
+ submitted: labels.questionnaireSubmitted,
2415
+ validationPrefix: labels.questionnaireValidationPrefix,
2416
+ submitFailed: labels.questionnaireSubmitFailed
2417
+ },
2185
2418
  onSubmit: canSubmitQuestionnaire ? (submission) => onQuestionnaireSubmit({
2186
2419
  ...submission,
2187
2420
  sourceMessageId: message.id
@@ -2214,14 +2447,31 @@ var ChatMessageItemView = ({
2214
2447
  "data-testid": block.tone === "fresh" ? "chat-message-fresh-block" : "chat-message-settled-block",
2215
2448
  "data-block-tone": block.tone,
2216
2449
  "data-block-index": index,
2217
- children: renderMarkdownContent(block.content)
2450
+ "data-render-mode": messageRenderMode,
2451
+ children: renderMessageContent(block.content)
2218
2452
  },
2219
2453
  `${block.tone}-${index}`
2220
2454
  )),
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
2455
+ !textBlocks.some((block) => block.content) && !settledText && !freshText && Boolean(textContent) ? /* @__PURE__ */ jsx8(
2456
+ ContentBlock,
2457
+ {
2458
+ "data-testid": "chat-message-settled-block",
2459
+ "data-block-tone": "settled",
2460
+ "data-render-mode": messageRenderMode,
2461
+ children: renderMessageContent(textContent)
2462
+ }
2463
+ ) : null
2222
2464
  ] });
2223
2465
  };
2224
- const renderStaticTextSegment = (content) => /* @__PURE__ */ jsx8(ContentBlock, { "data-testid": "chat-message-settled-block", "data-block-tone": "settled", children: renderMarkdownContent(content) });
2466
+ const renderStaticTextSegment = (content) => /* @__PURE__ */ jsx8(
2467
+ ContentBlock,
2468
+ {
2469
+ "data-testid": "chat-message-settled-block",
2470
+ "data-block-tone": "settled",
2471
+ "data-render-mode": messageRenderMode,
2472
+ children: renderMessageContent(content)
2473
+ }
2474
+ );
2225
2475
  const bodySegments = (() => {
2226
2476
  if (!shouldRenderStructuredBlocks && hasTextContent) {
2227
2477
  return [{ type: "text" }];
@@ -2279,21 +2529,40 @@ var ChatMessageItemView = ({
2279
2529
  }
2280
2530
  ) : null,
2281
2531
  /* @__PURE__ */ jsx8(Role, { children: message.role === "user" ? labels.userRoleLabel : labels.assistantRoleLabel }),
2532
+ isUserMessageCollapsible ? /* @__PURE__ */ jsx8(
2533
+ CollapseToggle,
2534
+ {
2535
+ type: "button",
2536
+ "data-testid": "chat-message-collapse-toggle",
2537
+ "aria-label": isUserMessageExpanded ? labels.collapseMessageAriaLabel : labels.expandMessageAriaLabel,
2538
+ "aria-expanded": isUserMessageExpanded,
2539
+ onClick: toggleUserMessageExpanded,
2540
+ children: /* @__PURE__ */ jsx8(CollapseIcon, { expanded: isUserMessageExpanded })
2541
+ }
2542
+ ) : null,
2282
2543
  isStoppedAssistant ? /* @__PURE__ */ jsx8(StatusTag, { "data-testid": "chat-message-stopped-tag", children: labels.stoppedResponse }) : null
2283
2544
  ] }),
2284
2545
  /* @__PURE__ */ jsxs5(Content, { "data-testid": "chat-message-content", children: [
2285
- shouldRenderStructuredBlocks || hasTextContent ? /* @__PURE__ */ jsx8(ContentStack, { "data-testid": "chat-message-body-stack", children: bodySegments.map((segment, index) => /* @__PURE__ */ jsx8(
2286
- ContentSegment,
2546
+ shouldRenderStructuredBlocks || hasTextContent ? /* @__PURE__ */ jsx8(
2547
+ ContentStack,
2287
2548
  {
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,
2549
+ ref: bodyStackRef,
2550
+ "data-testid": "chat-message-body-stack",
2551
+ "data-collapsed": isUserMessageCollapsed,
2552
+ children: bodySegments.map((segment, index) => /* @__PURE__ */ jsx8(
2553
+ ContentSegment,
2554
+ {
2555
+ "data-testid": "chat-message-content-segment",
2556
+ children: segment.type === "block" ? renderChatMessageBlock(segment.block, segment.index) : segment.type === "text" ? segment.content !== void 0 ? segment.useTimelineSegmentation ? renderTextContent({
2557
+ content: segment.content,
2558
+ displayedBlocks: segment.displayedBlocks,
2559
+ useTimelineSegmentation: true
2560
+ }) : renderStaticTextSegment(segment.content) : renderTextContent() : renderStaticTextSegment(segment.content)
2561
+ },
2562
+ segment.type === "text" ? `text-${index}` : segment.type === "markdown" ? `markdown-${index}` : `${segment.block.type}-${segment.index}`
2563
+ ))
2564
+ }
2565
+ ) : null,
2297
2566
  attachments.length ? /* @__PURE__ */ jsx8(AttachmentGrid, { "data-testid": "chat-message-attachment-grid", children: attachments.map((attachment) => /* @__PURE__ */ jsx8(
2298
2567
  AttachmentButton,
2299
2568
  {
@@ -2378,6 +2647,29 @@ var StatusTag = styled7.span`
2378
2647
  letter-spacing: 0.02em;
2379
2648
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
2380
2649
  `;
2650
+ var CollapseToggle = styled7.button`
2651
+ margin-left: auto;
2652
+ width: 28px;
2653
+ height: 28px;
2654
+ display: inline-flex;
2655
+ align-items: center;
2656
+ justify-content: center;
2657
+ border: 1px solid rgba(255, 255, 255, 0.12);
2658
+ border-radius: 999px;
2659
+ background: rgba(255, 255, 255, 0.06);
2660
+ color: rgba(255, 255, 255, 0.72);
2661
+ cursor: pointer;
2662
+ transition:
2663
+ background 160ms ease-out,
2664
+ border-color 160ms ease-out,
2665
+ color 160ms ease-out;
2666
+
2667
+ &:hover {
2668
+ background: rgba(255, 255, 255, 0.1);
2669
+ border-color: rgba(255, 255, 255, 0.18);
2670
+ color: rgba(255, 255, 255, 0.92);
2671
+ }
2672
+ `;
2381
2673
  var Content = styled7.div`
2382
2674
  color: rgba(255, 255, 255, 0.92);
2383
2675
  line-height: 1.6;
@@ -2422,6 +2714,11 @@ var ContentStack = styled7.div`
2422
2714
  display: flex;
2423
2715
  flex-direction: column;
2424
2716
  gap: 16px;
2717
+
2718
+ &[data-collapsed='true'] {
2719
+ max-height: ${USER_MESSAGE_COLLAPSE_HEIGHT_PX}px;
2720
+ overflow: hidden;
2721
+ }
2425
2722
  `;
2426
2723
  var ContentSegment = styled7.div`
2427
2724
  min-width: 0;
@@ -2435,6 +2732,11 @@ var ContentBlock = styled7.div`
2435
2732
  margin-top: 16px;
2436
2733
  }
2437
2734
 
2735
+ &[data-render-mode='plain-text'] {
2736
+ white-space: pre-wrap;
2737
+ overflow-wrap: anywhere;
2738
+ }
2739
+
2438
2740
  &[data-block-tone='fresh'] {
2439
2741
  opacity: 0.85;
2440
2742
  filter: brightness(0.82) saturate(0.88);
@@ -2685,7 +2987,7 @@ var ChatThreadView = ({
2685
2987
  onQuestionnaireSubmit,
2686
2988
  renderMessageBlock
2687
2989
  }) => {
2688
- const containerRef = useRef5(null);
2990
+ const containerRef = useRef4(null);
2689
2991
  const conversationTurns = useMemo4(
2690
2992
  () => groupConversationTurns(historyMessages, streamingMessage),
2691
2993
  [historyMessages, streamingMessage]
@@ -2693,10 +2995,10 @@ var ChatThreadView = ({
2693
2995
  const latestTurn = conversationTurns[conversationTurns.length - 1];
2694
2996
  const previousTurns = conversationTurns.slice(0, -1);
2695
2997
  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(() => {
2998
+ const latestUserMessageRef = useRef4(null);
2999
+ const reservedSpaceFrameRef = useRef4(null);
3000
+ const [latestTurnMinHeight, setLatestTurnMinHeight] = useState4(0);
3001
+ const measureLatestTurnMinHeight = useCallback3(() => {
2700
3002
  const container = containerRef.current;
2701
3003
  if (!container)
2702
3004
  return;
@@ -2706,7 +3008,7 @@ var ChatThreadView = ({
2706
3008
  const nextMinHeight = Math.max(0, container.clientHeight - paddingTop - paddingBottom);
2707
3009
  setLatestTurnMinHeight((current) => current === nextMinHeight ? current : nextMinHeight);
2708
3010
  }, []);
2709
- const scrollLatestUserMessageToTop = useCallback2(() => {
3011
+ const scrollLatestUserMessageToTop = useCallback3(() => {
2710
3012
  const container = containerRef.current;
2711
3013
  const target = latestUserMessageRef.current;
2712
3014
  if (!container || !target)
@@ -2726,7 +3028,7 @@ var ChatThreadView = ({
2726
3028
  }
2727
3029
  container.scrollTop = nextScrollTop;
2728
3030
  }, []);
2729
- useLayoutEffect(() => {
3031
+ useLayoutEffect2(() => {
2730
3032
  if (reservedSpaceFrameRef.current !== null) {
2731
3033
  window.cancelAnimationFrame(reservedSpaceFrameRef.current);
2732
3034
  reservedSpaceFrameRef.current = null;
@@ -2755,7 +3057,7 @@ var ChatThreadView = ({
2755
3057
  }
2756
3058
  };
2757
3059
  }, [latestUserMessageId, measureLatestTurnMinHeight, scrollLatestUserMessageToTop]);
2758
- useLayoutEffect(() => {
3060
+ useLayoutEffect2(() => {
2759
3061
  if (!latestUserMessageId)
2760
3062
  return;
2761
3063
  const handleResize = () => {
@@ -2842,15 +3144,40 @@ var ChatThread = () => {
2842
3144
  const error = useChatStore((s) => s.errorBySession[s.activeSessionId ?? ""]);
2843
3145
  const updateQA = useChatStore((s) => s.updateQuestionnaireAnswers);
2844
3146
  const clearSessionError = useChatStore((s) => s.clearSessionError);
2845
- const { sendRef, retryRef, renderMessageBlock, labels } = useChatContext();
2846
- const handleRetry = useCallback2(() => {
3147
+ const {
3148
+ sendRef,
3149
+ retryRef,
3150
+ renderMessageBlock,
3151
+ handleQuestionnaireSubmit: customQuestionnaireSubmit,
3152
+ handleConfirmationSubmit: customConfirmationSubmit,
3153
+ labels
3154
+ } = useChatContext();
3155
+ const handleRetry = useCallback3(() => {
2847
3156
  if (!activeSessionId)
2848
3157
  return;
2849
3158
  clearSessionError(activeSessionId);
2850
3159
  void retryRef.current();
2851
3160
  }, [activeSessionId, clearSessionError, retryRef]);
2852
- const handleQuestionnaireSubmit = useCallback2(
2853
- (submission) => {
3161
+ const handleQuestionnaireSubmit = useCallback3(
3162
+ async (submission) => {
3163
+ if (customQuestionnaireSubmit) {
3164
+ const handled = await customQuestionnaireSubmit(submission, {
3165
+ sessionId: activeSessionId ?? void 0,
3166
+ mode: activeSessionMode
3167
+ });
3168
+ if (handled !== false) {
3169
+ if (activeSessionId && submission.sourceMessageId) {
3170
+ updateQA(
3171
+ activeSessionId,
3172
+ submission.sourceMessageId,
3173
+ submission.questionnaireId,
3174
+ submission.answers
3175
+ );
3176
+ }
3177
+ return;
3178
+ }
3179
+ }
3180
+ await sendRef.current(submission.content);
2854
3181
  if (activeSessionId && submission.sourceMessageId) {
2855
3182
  updateQA(
2856
3183
  activeSessionId,
@@ -2859,15 +3186,23 @@ var ChatThread = () => {
2859
3186
  submission.answers
2860
3187
  );
2861
3188
  }
2862
- void sendRef.current(submission.content);
2863
3189
  },
2864
- [activeSessionId, updateQA, sendRef]
3190
+ [activeSessionId, activeSessionMode, updateQA, sendRef, customQuestionnaireSubmit]
2865
3191
  );
2866
- const handleConfirmationSubmit = useCallback2(
2867
- (submission) => {
2868
- void sendRef.current(submission.content);
3192
+ const handleConfirmation = useCallback3(
3193
+ async (submission) => {
3194
+ if (customConfirmationSubmit) {
3195
+ const handled = await customConfirmationSubmit(submission, {
3196
+ sessionId: activeSessionId ?? void 0,
3197
+ mode: activeSessionMode
3198
+ });
3199
+ if (handled !== false) {
3200
+ return;
3201
+ }
3202
+ }
3203
+ await sendRef.current(submission.content);
2869
3204
  },
2870
- [sendRef]
3205
+ [activeSessionId, activeSessionMode, sendRef, customConfirmationSubmit]
2871
3206
  );
2872
3207
  if (!hasSessions || messages.length === 0 && !streamingMessage) {
2873
3208
  return /* @__PURE__ */ jsx10(ChatThreadEmptyState, {});
@@ -2881,7 +3216,7 @@ var ChatThread = () => {
2881
3216
  error,
2882
3217
  retryButtonLabel: labels.retryButton,
2883
3218
  onRetry: handleRetry,
2884
- onConfirmationSubmit: handleConfirmationSubmit,
3219
+ onConfirmationSubmit: handleConfirmation,
2885
3220
  onQuestionnaireSubmit: handleQuestionnaireSubmit,
2886
3221
  renderMessageBlock
2887
3222
  }
@@ -2895,6 +3230,7 @@ var Container = styled9.div`
2895
3230
  min-height: 0;
2896
3231
  overflow: auto;
2897
3232
  padding: 24px;
3233
+ margin-bottom: 24px;
2898
3234
  overscroll-behavior: contain;
2899
3235
 
2900
3236
  &::-webkit-scrollbar {
@@ -2948,7 +3284,7 @@ var RetryButton = styled9.button`
2948
3284
  `;
2949
3285
 
2950
3286
  // src/components/chat-composer/index.tsx
2951
- import { useEffect as useEffect6, useRef as useRef8 } from "react";
3287
+ import { useEffect as useEffect6, useLayoutEffect as useLayoutEffect3, useRef as useRef7, useState as useState8 } from "react";
2952
3288
  import styled14 from "@emotion/styled";
2953
3289
 
2954
3290
  // src/components/chat-composer/lib/chat-composer.ts
@@ -3060,10 +3396,10 @@ var resolveSendSession = ({
3060
3396
  };
3061
3397
 
3062
3398
  // src/components/chat-composer/hooks/use-chat-composer.ts
3063
- import { useCallback as useCallback3, useEffect as useEffect5, useRef as useRef7, useState as useState7 } from "react";
3399
+ import { useCallback as useCallback4, useEffect as useEffect5, useRef as useRef6, useState as useState6 } from "react";
3064
3400
 
3065
3401
  // src/components/chat-composer/hooks/use-composer-attachments.ts
3066
- import { useEffect as useEffect4, useRef as useRef6, useState as useState6 } from "react";
3402
+ import { useEffect as useEffect4, useRef as useRef5, useState as useState5 } from "react";
3067
3403
  var SUPPORTED_IMAGE_MIME_TYPES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/webp"]);
3068
3404
  var MAX_COMPOSER_ATTACHMENTS = 10;
3069
3405
  var createObjectUrl = (file) => typeof URL !== "undefined" && typeof URL.createObjectURL === "function" ? URL.createObjectURL(file) : "";
@@ -3077,8 +3413,8 @@ var releaseComposerAttachments = (attachments) => {
3077
3413
  attachments.forEach((attachment) => revokeObjectUrl(attachment.previewUrl));
3078
3414
  };
3079
3415
  var useComposerAttachments = () => {
3080
- const [attachments, setAttachments] = useState6([]);
3081
- const attachmentsRef = useRef6([]);
3416
+ const [attachments, setAttachments] = useState5([]);
3417
+ const attachmentsRef = useRef5([]);
3082
3418
  useEffect4(() => {
3083
3419
  attachmentsRef.current = attachments;
3084
3420
  }, [attachments]);
@@ -3195,10 +3531,10 @@ var useChatComposer = () => {
3195
3531
  const clearSessionError = useChatStore((s) => s.clearSessionError);
3196
3532
  const setPreferredMode = useChatStore((s) => s.setPreferredMode);
3197
3533
  const setSessionMode = useChatStore((s) => s.setSessionMode);
3198
- const [availableModels, setAvailableModels] = useState7([]);
3199
- const [isModelsLoading, setIsModelsLoading] = useState7(true);
3200
- const [isModelsError, setIsModelsError] = useState7(false);
3201
- const fetchModels = useCallback3(async () => {
3534
+ const [availableModels, setAvailableModels] = useState6([]);
3535
+ const [isModelsLoading, setIsModelsLoading] = useState6(true);
3536
+ const [isModelsError, setIsModelsError] = useState6(false);
3537
+ const fetchModels = useCallback4(async () => {
3202
3538
  setIsModelsLoading(true);
3203
3539
  setIsModelsError(false);
3204
3540
  try {
@@ -3214,14 +3550,14 @@ var useChatComposer = () => {
3214
3550
  void fetchModels();
3215
3551
  }, [fetchModels]);
3216
3552
  const hasModels = availableModels.length > 0;
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);
3553
+ const [value, setValue] = useState6("");
3554
+ const [selectedModel, setSelectedModel] = useState6("");
3555
+ const [selectedMode, setSelectedModeLocal] = useState6(DEFAULT_CHAT_AGENT_MODE);
3556
+ const [attachmentNotice, setAttachmentNotice] = useState6(null);
3221
3557
  const { attachments, appendFiles, removeAttachment, takeMessageAttachments } = useComposerAttachments();
3222
- const abortControllerRef = useRef7(null);
3223
- const stopRequestRef = useRef7(null);
3224
- const lastRequestRef = useRef7(null);
3558
+ const abortControllerRef = useRef6(null);
3559
+ const stopRequestRef = useRef6(null);
3560
+ const lastRequestRef = useRef6(null);
3225
3561
  useEffect5(() => {
3226
3562
  setSelectedModel(
3227
3563
  (current) => resolveSelectedChatModel({ currentModel: current, availableModels, isModelsLoading })
@@ -3253,7 +3589,7 @@ var useChatComposer = () => {
3253
3589
  stopRequestRef.current.timeoutId = null;
3254
3590
  }
3255
3591
  };
3256
- const clearStopRequest = useCallback3((sessionId) => {
3592
+ const clearStopRequest = useCallback4((sessionId) => {
3257
3593
  if (!stopRequestRef.current)
3258
3594
  return;
3259
3595
  if (sessionId && stopRequestRef.current.sessionId !== sessionId)
@@ -3261,7 +3597,7 @@ var useChatComposer = () => {
3261
3597
  clearStopTimeout(sessionId);
3262
3598
  stopRequestRef.current = null;
3263
3599
  }, []);
3264
- const finalizeStop = useCallback3(
3600
+ const finalizeStop = useCallback4(
3265
3601
  (sessionId) => {
3266
3602
  if (stopRequestRef.current?.sessionId === sessionId) {
3267
3603
  if (stopRequestRef.current.finalized)
@@ -3276,7 +3612,7 @@ var useChatComposer = () => {
3276
3612
  },
3277
3613
  [clearStopRequest, finalizeStoppedStreamingMessage]
3278
3614
  );
3279
- const runStream = useCallback3(
3615
+ const runStream = useCallback4(
3280
3616
  async ({
3281
3617
  localSessionId,
3282
3618
  sessionId,
@@ -3365,7 +3701,7 @@ var useChatComposer = () => {
3365
3701
  setSessionError
3366
3702
  ]
3367
3703
  );
3368
- const send = useCallback3(
3704
+ const send = useCallback4(
3369
3705
  async (contentOverride) => {
3370
3706
  const content = (contentOverride ?? value).trim();
3371
3707
  const hasText = Boolean(content);
@@ -3513,14 +3849,14 @@ var useChatComposer = () => {
3513
3849
  };
3514
3850
 
3515
3851
  // src/components/chat-composer/components/chat-composer-attachment-list.tsx
3516
- import { useState as useState8 } from "react";
3852
+ import { useState as useState7 } from "react";
3517
3853
  import styled10 from "@emotion/styled";
3518
3854
  import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs8 } from "@emotion/react/jsx-runtime";
3519
3855
  var ChatComposerAttachmentList = ({
3520
3856
  attachments,
3521
3857
  onRemoveAttachment
3522
3858
  }) => {
3523
- const [activeImage, setActiveImage] = useState8(null);
3859
+ const [activeImage, setActiveImage] = useState7(null);
3524
3860
  if (!attachments.length) {
3525
3861
  return null;
3526
3862
  }
@@ -3974,6 +4310,27 @@ var StopSpinner = styled13.span`
3974
4310
 
3975
4311
  // src/components/chat-composer/index.tsx
3976
4312
  import { jsx as jsx15, jsxs as jsxs10 } from "@emotion/react/jsx-runtime";
4313
+ var CHAT_COMPOSER_LINE_HEIGHT_PX = 20;
4314
+ var CHAT_COMPOSER_MAX_ROWS = 7;
4315
+ var CHAT_COMPOSER_PADDING_TOP_PX = 8;
4316
+ var CHAT_COMPOSER_PADDING_BOTTOM_PX = 12;
4317
+ var CHAT_COMPOSER_PADDING_BLOCK_PX = CHAT_COMPOSER_PADDING_TOP_PX + CHAT_COMPOSER_PADDING_BOTTOM_PX;
4318
+ var CHAT_COMPOSER_MIN_ROWS = 4;
4319
+ var CHAT_COMPOSER_EXPANDED_MAX_ROWS = 60;
4320
+ var CHAT_COMPOSER_EXPANDED_MAX_VIEWPORT_RATIO = 0.7;
4321
+ var CHAT_COMPOSER_EXPANDED_RESERVED_SPACE_PX = 96;
4322
+ var CHAT_COMPOSER_MAX_HEIGHT_PX = CHAT_COMPOSER_MAX_ROWS * CHAT_COMPOSER_LINE_HEIGHT_PX + CHAT_COMPOSER_PADDING_BLOCK_PX;
4323
+ var CHAT_COMPOSER_EXPANDED_ROWS_HEIGHT_PX = CHAT_COMPOSER_EXPANDED_MAX_ROWS * CHAT_COMPOSER_LINE_HEIGHT_PX + CHAT_COMPOSER_PADDING_BLOCK_PX;
4324
+ var getExpandedComposerHeightPx = () => {
4325
+ if (typeof window === "undefined") {
4326
+ return CHAT_COMPOSER_EXPANDED_ROWS_HEIGHT_PX;
4327
+ }
4328
+ const viewportLimitedHeight = window.innerHeight * CHAT_COMPOSER_EXPANDED_MAX_VIEWPORT_RATIO - CHAT_COMPOSER_EXPANDED_RESERVED_SPACE_PX;
4329
+ return Math.max(
4330
+ CHAT_COMPOSER_MAX_HEIGHT_PX,
4331
+ Math.floor(Math.min(CHAT_COMPOSER_EXPANDED_ROWS_HEIGHT_PX, viewportLimitedHeight))
4332
+ );
4333
+ };
3977
4334
  var PlusIcon = () => /* @__PURE__ */ jsx15(
3978
4335
  "svg",
3979
4336
  {
@@ -3986,6 +4343,36 @@ var PlusIcon = () => /* @__PURE__ */ jsx15(
3986
4343
  children: /* @__PURE__ */ jsx15("path", { d: "M8 3v10M3 8h10", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round" })
3987
4344
  }
3988
4345
  );
4346
+ var ComposerExpandIcon = ({ expanded }) => /* @__PURE__ */ jsx15(
4347
+ "svg",
4348
+ {
4349
+ "aria-hidden": "true",
4350
+ width: "16",
4351
+ height: "16",
4352
+ viewBox: "0 0 16 16",
4353
+ fill: "none",
4354
+ xmlns: "http://www.w3.org/2000/svg",
4355
+ children: expanded ? /* @__PURE__ */ jsx15(
4356
+ "path",
4357
+ {
4358
+ d: "M14 6h-4V2M10 6l4-4M2 10h4v4M6 10l-4 4",
4359
+ stroke: "currentColor",
4360
+ strokeWidth: "1.5",
4361
+ strokeLinecap: "round",
4362
+ strokeLinejoin: "round"
4363
+ }
4364
+ ) : /* @__PURE__ */ jsx15(
4365
+ "path",
4366
+ {
4367
+ d: "M10 2h4v4M14 2L9 7M6 14H2v-4M2 14l5-5",
4368
+ stroke: "currentColor",
4369
+ strokeWidth: "1.5",
4370
+ strokeLinecap: "round",
4371
+ strokeLinejoin: "round"
4372
+ }
4373
+ )
4374
+ }
4375
+ );
3989
4376
  var ChatComposerView = ({
3990
4377
  value,
3991
4378
  placeholder,
@@ -4002,6 +4389,8 @@ var ChatComposerView = ({
4002
4389
  isStopping,
4003
4390
  enableImageAttachments,
4004
4391
  modeLabels,
4392
+ expandComposerAriaLabel,
4393
+ collapseComposerAriaLabel,
4005
4394
  onValueChange,
4006
4395
  onPickImages,
4007
4396
  onPasteImages,
@@ -4012,7 +4401,10 @@ var ChatComposerView = ({
4012
4401
  onStop,
4013
4402
  onSend
4014
4403
  }) => {
4015
- const imageInputRef = useRef8(null);
4404
+ const imageInputRef = useRef7(null);
4405
+ const inputRef = useRef7(null);
4406
+ const [isComposerExpandable, setIsComposerExpandable] = useState8(false);
4407
+ const [isComposerExpanded, setIsComposerExpanded] = useState8(false);
4016
4408
  const canSend = canSendChatMessage({
4017
4409
  value,
4018
4410
  attachmentCount: attachments.length,
@@ -4020,6 +4412,26 @@ var ChatComposerView = ({
4020
4412
  isModelsError,
4021
4413
  hasModels
4022
4414
  });
4415
+ useLayoutEffect3(() => {
4416
+ const element = inputRef.current;
4417
+ if (!element) {
4418
+ return;
4419
+ }
4420
+ if (!isComposerExpanded) {
4421
+ element.style.height = "0px";
4422
+ }
4423
+ const scrollHeight = element.scrollHeight;
4424
+ const nextExpandable = scrollHeight > CHAT_COMPOSER_MAX_HEIGHT_PX;
4425
+ const expandedHeight = getExpandedComposerHeightPx();
4426
+ const nextHeight = isComposerExpanded ? expandedHeight : Math.min(scrollHeight, CHAT_COMPOSER_MAX_HEIGHT_PX);
4427
+ setIsComposerExpandable(nextExpandable);
4428
+ element.style.height = `${nextHeight}px`;
4429
+ element.style.overflowY = !isComposerExpanded && scrollHeight > CHAT_COMPOSER_MAX_HEIGHT_PX ? "auto" : "hidden";
4430
+ }, [isComposerExpanded, value]);
4431
+ const handleSend = async () => {
4432
+ setIsComposerExpanded(false);
4433
+ await onSend();
4434
+ };
4023
4435
  const handleKeyDown = (event) => {
4024
4436
  if (shouldStopChatComposer({ key: event.key, shiftKey: event.shiftKey, isStreaming })) {
4025
4437
  event.preventDefault();
@@ -4029,7 +4441,7 @@ var ChatComposerView = ({
4029
4441
  if (!shouldSubmitChatComposer({ key: event.key, shiftKey: event.shiftKey, canSend }))
4030
4442
  return;
4031
4443
  event.preventDefault();
4032
- void onSend();
4444
+ void handleSend();
4033
4445
  };
4034
4446
  const handlePickImages = (event) => {
4035
4447
  if (event.target.files?.length)
@@ -4064,60 +4476,77 @@ var ChatComposerView = ({
4064
4476
  }
4065
4477
  ),
4066
4478
  attachmentNotice === "limit_reached" ? /* @__PURE__ */ jsx15(AttachmentNotice, { "data-testid": "chat-composer-attachment-notice", children: attachmentLimitNotice }) : null,
4067
- /* @__PURE__ */ jsx15(
4068
- Input,
4069
- {
4070
- "data-testid": "chat-composer-input",
4071
- value,
4072
- onChange: (event) => onValueChange(event.target.value),
4073
- onKeyDown: handleKeyDown,
4074
- onPaste: enableImageAttachments ? handlePaste : void 0,
4075
- placeholder
4076
- }
4077
- ),
4078
- /* @__PURE__ */ jsx15(Footer, { children: /* @__PURE__ */ jsxs10(Actions2, { "data-testid": "chat-composer-actions", children: [
4079
- enableImageAttachments ? /* @__PURE__ */ jsx15(
4080
- AttachButton,
4479
+ /* @__PURE__ */ jsxs10(InputArea, { "data-testid": "chat-composer-input-area", children: [
4480
+ isComposerExpanded || isComposerExpandable ? /* @__PURE__ */ jsx15(
4481
+ ComposerExpandButton,
4081
4482
  {
4082
4483
  type: "button",
4083
- "data-testid": "chat-composer-attach-image",
4084
- "aria-label": "Attach image",
4085
- onClick: () => imageInputRef.current?.click(),
4086
- children: /* @__PURE__ */ jsx15(PlusIcon, {})
4484
+ "data-testid": "chat-composer-expand-toggle",
4485
+ "aria-label": isComposerExpanded ? collapseComposerAriaLabel : expandComposerAriaLabel,
4486
+ "aria-expanded": isComposerExpanded,
4487
+ onClick: () => setIsComposerExpanded((current) => !current),
4488
+ children: /* @__PURE__ */ jsx15(ComposerExpandIcon, { expanded: isComposerExpanded })
4087
4489
  }
4088
4490
  ) : null,
4089
4491
  /* @__PURE__ */ jsx15(
4090
- ChatModeControl,
4091
- {
4092
- value: selectedMode,
4093
- disabled: isStreaming,
4094
- labels: modeLabels,
4095
- onChange: onSelectedModeChange
4096
- }
4097
- ),
4098
- /* @__PURE__ */ jsx15(
4099
- ChatModelControl,
4492
+ Input,
4100
4493
  {
4101
- selectedModel,
4102
- availableModels,
4103
- isModelsLoading,
4104
- isModelsError,
4105
- hasModels,
4106
- onSelectedModelChange,
4107
- onReloadModels
4494
+ ref: inputRef,
4495
+ "data-testid": "chat-composer-input",
4496
+ "data-expanded": isComposerExpanded,
4497
+ value,
4498
+ onChange: (event) => onValueChange(event.target.value),
4499
+ onKeyDown: handleKeyDown,
4500
+ onPaste: enableImageAttachments ? handlePaste : void 0,
4501
+ placeholder
4108
4502
  }
4109
- ),
4110
- /* @__PURE__ */ jsx15(
4111
- ChatSendActions,
4503
+ )
4504
+ ] }),
4505
+ /* @__PURE__ */ jsxs10(Footer, { children: [
4506
+ /* @__PURE__ */ jsx15(LeadingActions, { "data-testid": "chat-composer-leading-actions", children: enableImageAttachments ? /* @__PURE__ */ jsx15(
4507
+ AttachButton,
4112
4508
  {
4113
- canSend,
4114
- isStreaming,
4115
- isStopping,
4116
- onStop,
4117
- onSend
4509
+ type: "button",
4510
+ "data-testid": "chat-composer-attach-image",
4511
+ "aria-label": "Attach image",
4512
+ onClick: () => imageInputRef.current?.click(),
4513
+ children: /* @__PURE__ */ jsx15(PlusIcon, {})
4118
4514
  }
4119
- )
4120
- ] }) })
4515
+ ) : null }),
4516
+ /* @__PURE__ */ jsxs10(TrailingActions, { "data-testid": "chat-composer-trailing-actions", children: [
4517
+ /* @__PURE__ */ jsx15(
4518
+ ChatModeControl,
4519
+ {
4520
+ value: selectedMode,
4521
+ disabled: isStreaming,
4522
+ labels: modeLabels,
4523
+ onChange: onSelectedModeChange
4524
+ }
4525
+ ),
4526
+ /* @__PURE__ */ jsx15(
4527
+ ChatModelControl,
4528
+ {
4529
+ selectedModel,
4530
+ availableModels,
4531
+ isModelsLoading,
4532
+ isModelsError,
4533
+ hasModels,
4534
+ onSelectedModelChange,
4535
+ onReloadModels
4536
+ }
4537
+ ),
4538
+ /* @__PURE__ */ jsx15(
4539
+ ChatSendActions,
4540
+ {
4541
+ canSend,
4542
+ isStreaming,
4543
+ isStopping,
4544
+ onStop,
4545
+ onSend: handleSend
4546
+ }
4547
+ )
4548
+ ] })
4549
+ ] })
4121
4550
  ] }) });
4122
4551
  };
4123
4552
  var ChatComposer = () => {
@@ -4153,6 +4582,8 @@ var ChatComposer = () => {
4153
4582
  isStopping: state.isStopping,
4154
4583
  enableImageAttachments,
4155
4584
  modeLabels,
4585
+ expandComposerAriaLabel: labels.expandComposerAriaLabel,
4586
+ collapseComposerAriaLabel: labels.collapseComposerAriaLabel,
4156
4587
  onValueChange: actions.setValue,
4157
4588
  onPickImages: actions.pickImages,
4158
4589
  onPasteImages: actions.pasteImages,
@@ -4169,6 +4600,16 @@ var Container2 = styled14.div`
4169
4600
  padding: 0 16px 16px;
4170
4601
  `;
4171
4602
  var Surface = styled14.div`
4603
+ display: grid;
4604
+ grid-template-columns: minmax(0, 1fr);
4605
+ grid-template-areas:
4606
+ 'attachments'
4607
+ 'notice'
4608
+ 'input'
4609
+ 'footer';
4610
+ width: 100%;
4611
+ max-width: 760px;
4612
+ margin: 0 auto;
4172
4613
  background: var(--border-color);
4173
4614
  border-radius: 20px;
4174
4615
  border: 1px solid var(--border-hover);
@@ -4178,6 +4619,7 @@ var Surface = styled14.div`
4178
4619
  backdrop-filter: blur(10px);
4179
4620
  `;
4180
4621
  var AttachmentNotice = styled14.div`
4622
+ grid-area: notice;
4181
4623
  margin: 10px 12px 0;
4182
4624
  padding: 8px 10px;
4183
4625
  border-radius: 10px;
@@ -4187,19 +4629,45 @@ var AttachmentNotice = styled14.div`
4187
4629
  font-size: 12px;
4188
4630
  line-height: 1.4;
4189
4631
  `;
4632
+ var InputArea = styled14.div`
4633
+ grid-area: input;
4634
+ position: relative;
4635
+ `;
4190
4636
  var Input = styled14.textarea`
4637
+ --textarea-line-height: ${CHAT_COMPOSER_LINE_HEIGHT_PX}px;
4638
+ --textarea-min-rows: ${CHAT_COMPOSER_MIN_ROWS};
4639
+ --textarea-max-rows: ${CHAT_COMPOSER_MAX_ROWS};
4640
+ --textarea-expanded-max-rows: ${CHAT_COMPOSER_EXPANDED_MAX_ROWS};
4641
+ --textarea-padding-top: ${CHAT_COMPOSER_PADDING_TOP_PX}px;
4642
+ --textarea-padding-bottom: ${CHAT_COMPOSER_PADDING_BOTTOM_PX}px;
4643
+ --textarea-padding-block: calc(var(--textarea-padding-top) + var(--textarea-padding-bottom));
4644
+ --textarea-max-height: calc(
4645
+ var(--textarea-max-rows) * var(--textarea-line-height) + var(--textarea-padding-block)
4646
+ );
4647
+ --textarea-expanded-max-height: min(
4648
+ calc(
4649
+ var(--textarea-expanded-max-rows) * var(--textarea-line-height) +
4650
+ var(--textarea-padding-block)
4651
+ ),
4652
+ calc(70vh - ${CHAT_COMPOSER_EXPANDED_RESERVED_SPACE_PX}px)
4653
+ );
4191
4654
  width: 100%;
4192
- min-height: 96px;
4655
+ min-height: calc(
4656
+ var(--textarea-min-rows) * var(--textarea-line-height) + var(--textarea-padding-block)
4657
+ );
4658
+ max-height: var(--textarea-max-height);
4659
+ box-sizing: border-box;
4193
4660
  resize: none;
4194
4661
  appearance: none;
4195
4662
  border: 0;
4196
4663
  outline: 0;
4197
4664
  background: transparent;
4198
- padding: 8px 12px 12px 12px;
4665
+ padding: var(--textarea-padding-top) 44px var(--textarea-padding-bottom) 12px;
4199
4666
  font-weight: 400;
4200
4667
  font-size: 14px;
4201
4668
  color: var(--text-primary);
4202
- line-height: 20px;
4669
+ line-height: var(--textarea-line-height);
4670
+ overflow-y: hidden;
4203
4671
 
4204
4672
  &::placeholder {
4205
4673
  color: var(--text-secondary);
@@ -4208,19 +4676,50 @@ var Input = styled14.textarea`
4208
4676
  &::-webkit-resizer {
4209
4677
  display: none;
4210
4678
  }
4679
+
4680
+ &[data-expanded='true'] {
4681
+ max-height: var(--textarea-expanded-max-height);
4682
+ }
4683
+ `;
4684
+ var ComposerExpandButton = styled14.button`
4685
+ position: absolute;
4686
+ top: 8px;
4687
+ right: 10px;
4688
+ width: 28px;
4689
+ height: 28px;
4690
+ display: grid;
4691
+ place-items: center;
4692
+ border: none;
4693
+ border-radius: 999px;
4694
+ background: transparent;
4695
+ color: rgba(255, 255, 255, 0.72);
4696
+ cursor: pointer;
4697
+ z-index: 1;
4698
+
4699
+ &:hover {
4700
+ background: rgba(255, 255, 255, 0.08);
4701
+ color: rgba(255, 255, 255, 0.92);
4702
+ }
4211
4703
  `;
4212
4704
  var Footer = styled14.div`
4213
- display: flex;
4705
+ grid-area: footer;
4706
+ display: grid;
4707
+ grid-template-columns: minmax(0, 1fr) auto;
4214
4708
  align-items: flex-end;
4215
- justify-content: stretch;
4216
4709
  gap: 16px;
4217
4710
  padding: 0 14px 14px;
4218
4711
  `;
4219
- var Actions2 = styled14.div`
4712
+ var LeadingActions = styled14.div`
4713
+ display: flex;
4714
+ align-items: center;
4715
+ justify-content: flex-start;
4716
+ gap: 8px;
4717
+ min-width: 0;
4718
+ `;
4719
+ var TrailingActions = styled14.div`
4220
4720
  display: flex;
4221
4721
  align-items: center;
4222
4722
  flex-wrap: wrap;
4223
- width: 100%;
4224
4723
  min-width: 0;
4225
4724
  justify-content: flex-end;
4226
4725
  gap: 8px;