@xinghunm/ai-chat 0.4.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -78,10 +78,18 @@ var DEFAULT_AI_CHAT_LABELS = {
78
78
  attachmentLimitNotice: "Images exceeded the limit. Only the first 10 images were kept.",
79
79
  userRoleLabel: "User",
80
80
  assistantRoleLabel: "Assistant",
81
+ expandMessageAriaLabel: "Expand message",
82
+ collapseMessageAriaLabel: "Collapse message",
83
+ expandComposerAriaLabel: "Expand composer",
84
+ collapseComposerAriaLabel: "Collapse composer",
81
85
  stoppedResponse: "Response stopped",
82
86
  assistantStreamingAriaLabel: "assistant streaming",
83
87
  networkError: "Network request failed. Please try again.",
84
- genericError: "Something went wrong. Please try again."
88
+ genericError: "Something went wrong. Please try again.",
89
+ questionnaireSubmitting: "Submitting...",
90
+ questionnaireSubmitted: "Selection submitted. Waiting for the plan to continue...",
91
+ questionnaireValidationPrefix: "Please complete:",
92
+ questionnaireSubmitFailed: "Failed to submit. Please try again."
85
93
  };
86
94
 
87
95
  // src/store/chat-store.ts
@@ -97,6 +105,40 @@ var resolveSessionTitleFromMessage = (message) => {
97
105
  }
98
106
  return DEFAULT_CHAT_SESSION_TITLE;
99
107
  };
108
+ var mergeStreamingBlocks = (existingBlocks, incomingBlocks) => {
109
+ const nextBlocks = [...existingBlocks ?? []];
110
+ incomingBlocks.forEach((incomingBlock) => {
111
+ if (incomingBlock.type === "custom" && incomingBlock.blockKey) {
112
+ const mergePolicy = incomingBlock.mergePolicy ?? "append";
113
+ if (mergePolicy !== "append") {
114
+ const existingIndex2 = nextBlocks.findIndex(
115
+ (block) => block.type === "custom" && block.blockKey === incomingBlock.blockKey
116
+ );
117
+ if (existingIndex2 !== -1) {
118
+ if (mergePolicy === "replace") {
119
+ nextBlocks[existingIndex2] = incomingBlock;
120
+ }
121
+ return;
122
+ }
123
+ }
124
+ nextBlocks.push(incomingBlock);
125
+ return;
126
+ }
127
+ if (incomingBlock.type !== "questionnaire") {
128
+ nextBlocks.push(incomingBlock);
129
+ return;
130
+ }
131
+ const existingIndex = nextBlocks.findIndex(
132
+ (block) => block.type === "questionnaire" && block.questionnaire.questionnaireId === incomingBlock.questionnaire.questionnaireId
133
+ );
134
+ if (existingIndex === -1) {
135
+ nextBlocks.push(incomingBlock);
136
+ return;
137
+ }
138
+ nextBlocks[existingIndex] = incomingBlock;
139
+ });
140
+ return nextBlocks;
141
+ };
100
142
  var finalizeStreamingMessage = (state, sessionId, status, clearError = false) => {
101
143
  const message = state.streamingMessageBySession[sessionId];
102
144
  const hasRenderableMessagePayload = Boolean(
@@ -286,10 +328,15 @@ var createChatStore = (initialState) => (0, import_vanilla.createStore)((set, ge
286
328
  const target = state.streamingMessageBySession[sessionId];
287
329
  if (!target)
288
330
  return;
331
+ const nextBlocks = patch.blocks !== void 0 ? mergeStreamingBlocks(target.blocks, patch.blocks) : target.blocks;
289
332
  set({
290
333
  streamingMessageBySession: {
291
334
  ...state.streamingMessageBySession,
292
- [sessionId]: { ...target, ...patch }
335
+ [sessionId]: {
336
+ ...target,
337
+ ...patch,
338
+ ...patch.blocks !== void 0 ? { blocks: nextBlocks } : {}
339
+ }
293
340
  }
294
341
  });
295
342
  },
@@ -509,9 +556,27 @@ var DEFAULT_CHAT_TRANSPORT_ENDPOINTS = {
509
556
  completions: "/chat/completions",
510
557
  terminate: "/chat/terminate"
511
558
  };
559
+ var createToolExecutionHeaders = (policy) => {
560
+ if (!policy?.enabled) {
561
+ return {};
562
+ }
563
+ return {
564
+ "X-Tool-Approval-Required": String(Boolean(policy.approvalRequired)),
565
+ ...typeof policy.approvalTimeoutSec === "number" ? { "X-Tool-Approval-Timeout": String(policy.approvalTimeoutSec) } : {}
566
+ };
567
+ };
568
+ var createModeDefaultHeaders = (mode) => {
569
+ if (mode === "ask" || mode === "plan") {
570
+ return {
571
+ "X-Tool-Approval-Required": "false"
572
+ };
573
+ }
574
+ return {};
575
+ };
512
576
  var createDefaultChatTransport = ({
513
577
  apiBaseUrl,
514
578
  authToken,
579
+ toolExecutionPolicy,
515
580
  streamHeaders,
516
581
  transformStreamPacket,
517
582
  endpoints,
@@ -522,6 +587,10 @@ var createDefaultChatTransport = ({
522
587
  ...DEFAULT_CHAT_TRANSPORT_ENDPOINTS,
523
588
  ...endpoints
524
589
  };
590
+ const resolvedStreamHeaders = {
591
+ ...createToolExecutionHeaders(toolExecutionPolicy),
592
+ ...streamHeaders
593
+ };
525
594
  return {
526
595
  getModels: () => getChatModels(client, resolvedEndpoints.models),
527
596
  startStream: async ({
@@ -535,12 +604,16 @@ var createDefaultChatTransport = ({
535
604
  onDone,
536
605
  onError
537
606
  }) => {
607
+ const requestHeaders = {
608
+ ...createModeDefaultHeaders(mode),
609
+ ...resolvedStreamHeaders
610
+ };
538
611
  await startChatStream({
539
612
  apiBaseUrl,
540
613
  endpointPath: resolvedEndpoints.completions,
541
614
  sessionId,
542
615
  authToken,
543
- requestHeaders: streamHeaders,
616
+ requestHeaders,
544
617
  model,
545
618
  mode,
546
619
  content,
@@ -569,6 +642,8 @@ var AiChatProvider = (props) => {
569
642
  defaultMode,
570
643
  labels,
571
644
  renderMessageBlock,
645
+ handleQuestionnaireSubmit,
646
+ handleConfirmationSubmit,
572
647
  messageRenderOrder,
573
648
  enableImageAttachments = true,
574
649
  children
@@ -624,6 +699,8 @@ var AiChatProvider = (props) => {
624
699
  sendRef,
625
700
  retryRef,
626
701
  renderMessageBlock,
702
+ handleQuestionnaireSubmit,
703
+ handleConfirmationSubmit,
627
704
  messageRenderOrder,
628
705
  transformStreamPacket: defaultTransformStreamPacket,
629
706
  enableImageAttachments
@@ -634,6 +711,8 @@ var AiChatProvider = (props) => {
634
711
  defaultAuthToken,
635
712
  defaultTransformStreamPacket,
636
713
  enableImageAttachments,
714
+ handleConfirmationSubmit,
715
+ handleQuestionnaireSubmit,
637
716
  labels,
638
717
  messageRenderOrder,
639
718
  renderMessageBlock,
@@ -943,11 +1022,11 @@ var getTimelineBlockKey = (block, index) => {
943
1022
  case "confirmation_card":
944
1023
  return `${index}:confirmation_card:${block.proposal.proposalId}`;
945
1024
  case "result_summary":
946
- return `${index}:result_summary:${block.summary.taskId}:${block.summary.status}`;
1025
+ return `${index}:result_summary:${block.summary.summaryId}:${block.summary.status}`;
947
1026
  case "questionnaire":
948
1027
  return `${index}:questionnaire:${block.questionnaire.questionnaireId}`;
949
1028
  case "custom":
950
- return `${index}:custom:${block.kind}:${stringifyTimelineKeyPart(block.data)}`;
1029
+ return block.blockKey ? `custom:${block.blockKey}` : `${index}:custom:${block.kind}:${stringifyTimelineKeyPart(block.data)}`;
951
1030
  default:
952
1031
  return null;
953
1032
  }
@@ -1235,21 +1314,21 @@ var useTimelineBlockAnchors = ({
1235
1314
  };
1236
1315
  };
1237
1316
 
1238
- // src/components/chat-thread/components/pde-ai-execution-confirmation-card.tsx
1317
+ // src/components/chat-thread/components/execution-confirmation-card.tsx
1239
1318
  var import_styled = __toESM(require("@emotion/styled"));
1240
1319
  var import_jsx_runtime2 = require("@emotion/react/jsx-runtime");
1241
- var PDEAIExecutionConfirmationCard = ({
1320
+ var ExecutionConfirmationCard = ({
1242
1321
  proposal,
1243
1322
  interactive = false,
1244
1323
  onApply,
1245
1324
  onConfirm,
1246
1325
  onRevise
1247
1326
  }) => {
1248
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Card, { "data-testid": "pde-ai-execution-confirmation-card", children: [
1327
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Card, { "data-testid": "execution-confirmation-card", children: [
1249
1328
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Header, { children: [
1250
1329
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Eyebrow, { children: "Execution Proposal" }),
1251
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Title, { children: proposal.equationName }),
1252
- proposal.solverName ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Subtitle, { children: proposal.solverName }) : null
1330
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Title, { children: proposal.resourceName }),
1331
+ proposal.executorName ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Subtitle, { children: proposal.executorName }) : null
1253
1332
  ] }),
1254
1333
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SummaryList, { children: proposal.parameterSummary.map((item) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(SummaryItem, { children: [
1255
1334
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SummaryLabel, { children: item.label }),
@@ -1257,21 +1336,13 @@ var PDEAIExecutionConfirmationCard = ({
1257
1336
  ] }, `${item.fieldPath ?? item.label}-${item.value}`)) }),
1258
1337
  proposal.warnings?.length ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Warnings, { children: proposal.warnings.map((warning) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Warning, { children: warning }, warning)) }) : null,
1259
1338
  interactive ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Actions, { children: [
1260
- onApply && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ActionButton, { type: "button", "data-testid": "pde-ai-confirmation-apply", onClick: onApply, children: "Apply to Parameters" }),
1261
- onConfirm && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1262
- ActionButton,
1263
- {
1264
- type: "button",
1265
- "data-testid": "pde-ai-confirmation-confirm",
1266
- onClick: onConfirm,
1267
- children: "Confirm Execution"
1268
- }
1269
- ),
1339
+ onApply && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ActionButton, { type: "button", "data-testid": "confirmation-apply", onClick: onApply, children: "Apply to Parameters" }),
1340
+ onConfirm && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ActionButton, { type: "button", "data-testid": "confirmation-confirm", onClick: onConfirm, children: "Confirm Execution" }),
1270
1341
  onRevise && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1271
1342
  SecondaryActionButton,
1272
1343
  {
1273
1344
  type: "button",
1274
- "data-testid": "pde-ai-confirmation-revise",
1345
+ "data-testid": "confirmation-revise",
1275
1346
  onClick: onRevise,
1276
1347
  children: "Revise Plan"
1277
1348
  }
@@ -1368,10 +1439,10 @@ var SecondaryActionButton = (0, import_styled.default)(ActionButton)`
1368
1439
  border: 1px solid rgba(255, 255, 255, 0.14);
1369
1440
  `;
1370
1441
 
1371
- // src/components/chat-thread/components/pde-ai-notice-card.tsx
1442
+ // src/components/chat-thread/components/notice-card.tsx
1372
1443
  var import_styled2 = __toESM(require("@emotion/styled"));
1373
1444
  var import_jsx_runtime3 = require("@emotion/react/jsx-runtime");
1374
- var PDEAINoticeCard = ({ text, tone }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Card2, { "data-testid": "pde-ai-notice-card", "data-tone": tone, children: text });
1445
+ var NoticeCard = ({ text, tone }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Card2, { "data-testid": "notice-card", "data-tone": tone, children: text });
1375
1446
  var Card2 = import_styled2.default.div`
1376
1447
  padding: 12px 14px;
1377
1448
  border-radius: 16px;
@@ -1397,10 +1468,10 @@ var Card2 = import_styled2.default.div`
1397
1468
  }
1398
1469
  `;
1399
1470
 
1400
- // src/components/chat-thread/components/pde-ai-parameter-summary-card.tsx
1471
+ // src/components/chat-thread/components/parameter-summary-card.tsx
1401
1472
  var import_styled3 = __toESM(require("@emotion/styled"));
1402
1473
  var import_jsx_runtime4 = require("@emotion/react/jsx-runtime");
1403
- var PDEAIParameterSummaryCard = ({ items }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Card3, { "data-testid": "pde-ai-parameter-summary-card", children: [
1474
+ var ParameterSummaryCard = ({ items }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Card3, { "data-testid": "parameter-summary-card", children: [
1404
1475
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Title2, { children: "Parameter Summary" }),
1405
1476
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(List, { children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(ListItem, { children: [
1406
1477
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Label, { children: item.label }),
@@ -1444,11 +1515,17 @@ var Value = import_styled3.default.span`
1444
1515
  text-align: right;
1445
1516
  `;
1446
1517
 
1447
- // src/components/chat-thread/components/pde-ai-questionnaire-card.tsx
1518
+ // src/components/chat-thread/components/questionnaire-card.tsx
1448
1519
  var import_react7 = require("react");
1449
1520
  var import_styled4 = __toESM(require("@emotion/styled"));
1450
1521
  var import_jsx_runtime5 = require("@emotion/react/jsx-runtime");
1451
1522
  var OTHER_OPTION_VALUE = "__other__";
1523
+ var DEFAULT_QUESTIONNAIRE_CARD_LABELS = {
1524
+ submitting: "Submitting...",
1525
+ submitted: "Selection submitted. Waiting for the plan to continue...",
1526
+ validationPrefix: "Please complete:",
1527
+ submitFailed: "Failed to submit. Please try again."
1528
+ };
1452
1529
  var createInitialAnswers = (questionnaire) => ({
1453
1530
  ...questionnaire.answers ?? {}
1454
1531
  });
@@ -1542,26 +1619,40 @@ var normalizeQuestionAnswer = (question, answer) => {
1542
1619
  return answer;
1543
1620
  }
1544
1621
  };
1545
- var PDEAIQuestionnaireCardInner = ({
1622
+ var QuestionnaireCardInner = ({
1546
1623
  questionnaire,
1547
1624
  interactive = false,
1548
- onSubmit
1625
+ onSubmit,
1626
+ labels
1549
1627
  }) => {
1550
1628
  const [answers, setAnswers] = (0, import_react7.useState)(
1551
1629
  () => createInitialAnswers(questionnaire)
1552
1630
  );
1553
1631
  const [errorMessage, setErrorMessage] = (0, import_react7.useState)(null);
1554
- const handleSubmit = () => {
1632
+ const [isSubmitting, setIsSubmitting] = (0, import_react7.useState)(false);
1633
+ const [isSubmitted, setIsSubmitted] = (0, import_react7.useState)(false);
1634
+ const resolvedLabels = {
1635
+ ...DEFAULT_QUESTIONNAIRE_CARD_LABELS,
1636
+ ...labels
1637
+ };
1638
+ const hasExternalFailureStatus = questionnaire.status === "expired" || questionnaire.status === "failed";
1639
+ const visibleErrorMessage = questionnaire.statusMessage ?? errorMessage;
1640
+ const isInteractionLocked = !interactive || isSubmitting || isSubmitted || hasExternalFailureStatus;
1641
+ const handleSubmit = async () => {
1642
+ if (isSubmitting || isSubmitted) {
1643
+ return;
1644
+ }
1555
1645
  const missingQuestions = questionnaire.questions.filter(
1556
1646
  (question) => question.required && isMissingRequiredAnswer(question, answers)
1557
1647
  );
1558
1648
  if (missingQuestions.length > 0) {
1559
1649
  setErrorMessage(
1560
- `Please complete: ${missingQuestions.map((question) => question.label).join(", ")}`
1650
+ `${resolvedLabels.validationPrefix} ${missingQuestions.map((question) => question.label).join(", ")}`
1561
1651
  );
1562
1652
  return;
1563
1653
  }
1564
1654
  setErrorMessage(null);
1655
+ setIsSubmitting(true);
1565
1656
  const normalizedAnswers = Object.fromEntries(
1566
1657
  questionnaire.questions.flatMap((question) => {
1567
1658
  const value = normalizeQuestionAnswer(question, answers[question.id]);
@@ -1578,11 +1669,18 @@ var PDEAIQuestionnaireCardInner = ({
1578
1669
  return [`- ${question.label}: ${formatQuestionAnswer(question, value)}`];
1579
1670
  })
1580
1671
  ];
1581
- onSubmit?.({
1582
- questionnaireId: questionnaire.questionnaireId,
1583
- answers: normalizedAnswers,
1584
- content: contentLines.join("\n")
1585
- });
1672
+ try {
1673
+ await onSubmit?.({
1674
+ questionnaireId: questionnaire.questionnaireId,
1675
+ answers: normalizedAnswers,
1676
+ content: contentLines.join("\n")
1677
+ });
1678
+ setIsSubmitted(true);
1679
+ } catch (error) {
1680
+ setErrorMessage(error instanceof Error ? error.message : resolvedLabels.submitFailed);
1681
+ } finally {
1682
+ setIsSubmitting(false);
1683
+ }
1586
1684
  };
1587
1685
  const renderQuestion = (question) => {
1588
1686
  const renderOptionChoice = ({
@@ -1597,13 +1695,13 @@ var PDEAIQuestionnaireCardInner = ({
1597
1695
  OptionChoiceItem,
1598
1696
  {
1599
1697
  role: "button",
1600
- tabIndex: interactive ? 0 : -1,
1698
+ tabIndex: isInteractionLocked ? -1 : 0,
1601
1699
  "aria-pressed": isSelected,
1602
1700
  "data-selected": isSelected,
1603
1701
  "data-tone": tone,
1604
- "data-testid": `pde-ai-question-option-${questionId}-${index}`,
1702
+ "data-testid": `question-option-${questionId}-${index}`,
1605
1703
  onClick: (event) => {
1606
- if (!interactive) {
1704
+ if (isInteractionLocked) {
1607
1705
  return;
1608
1706
  }
1609
1707
  if (event.target instanceof HTMLElement && event.target.closest("input")) {
@@ -1612,7 +1710,7 @@ var PDEAIQuestionnaireCardInner = ({
1612
1710
  onClick();
1613
1711
  },
1614
1712
  onKeyDown: (event) => {
1615
- if (!interactive) {
1713
+ if (isInteractionLocked) {
1616
1714
  return;
1617
1715
  }
1618
1716
  if (event.key !== "Enter" && event.key !== " ") {
@@ -1671,11 +1769,11 @@ var PDEAIQuestionnaireCardInner = ({
1671
1769
  inlineInput: singleSelectDraft.selectedValue === OTHER_OPTION_VALUE ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1672
1770
  InlineOtherInput,
1673
1771
  {
1674
- "data-testid": `pde-ai-question-input-${question.id}`,
1772
+ "data-testid": `question-input-${question.id}`,
1675
1773
  type: "text",
1676
1774
  value: singleSelectDraft.otherValue,
1677
1775
  placeholder: "Other",
1678
- readOnly: !interactive,
1776
+ readOnly: isInteractionLocked,
1679
1777
  onClick: (event) => {
1680
1778
  event.stopPropagation();
1681
1779
  },
@@ -1696,11 +1794,11 @@ var PDEAIQuestionnaireCardInner = ({
1696
1794
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(QuestionBody, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1697
1795
  TextInput,
1698
1796
  {
1699
- "data-testid": `pde-ai-question-input-${question.id}`,
1797
+ "data-testid": `question-input-${question.id}`,
1700
1798
  type: "text",
1701
1799
  value: getTextInputValue(answers[question.id]),
1702
1800
  placeholder: question.placeholder,
1703
- readOnly: !interactive,
1801
+ readOnly: isInteractionLocked,
1704
1802
  onChange: (event) => {
1705
1803
  setAnswers((current) => updateAnswerValue(current, question.id, event.target.value));
1706
1804
  }
@@ -1711,11 +1809,11 @@ var PDEAIQuestionnaireCardInner = ({
1711
1809
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1712
1810
  TextInput,
1713
1811
  {
1714
- "data-testid": `pde-ai-question-input-${question.id}`,
1812
+ "data-testid": `question-input-${question.id}`,
1715
1813
  type: "number",
1716
1814
  value: getNumberInputValue(answers[question.id]),
1717
1815
  placeholder: question.placeholder,
1718
- readOnly: !interactive,
1816
+ readOnly: isInteractionLocked,
1719
1817
  onChange: (event) => {
1720
1818
  setAnswers(
1721
1819
  (current) => updateAnswerValue(
@@ -1750,7 +1848,7 @@ var PDEAIQuestionnaireCardInner = ({
1750
1848
  return null;
1751
1849
  }
1752
1850
  };
1753
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Card4, { "data-testid": "pde-ai-questionnaire-card", children: [
1851
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Card4, { "data-testid": "questionnaire-card", children: [
1754
1852
  questionnaire.title ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Title3, { children: questionnaire.title }) : null,
1755
1853
  questionnaire.description ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Description, { children: questionnaire.description }) : null,
1756
1854
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(QuestionList, { children: questionnaire.questions.map((question) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(QuestionCard, { children: [
@@ -1760,24 +1858,28 @@ var PDEAIQuestionnaireCardInner = ({
1760
1858
  ] }),
1761
1859
  renderQuestion(question)
1762
1860
  ] }, question.id)) }),
1763
- errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ErrorMessage, { "data-testid": "pde-ai-questionnaire-error", children: errorMessage }) : null,
1764
- interactive ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1861
+ visibleErrorMessage ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ErrorMessage, { "data-testid": "questionnaire-error", children: visibleErrorMessage }) : null,
1862
+ isSubmitted ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SuccessMessage, { "data-testid": "questionnaire-success", children: resolvedLabels.submitted }) : interactive && !hasExternalFailureStatus ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1765
1863
  SubmitButton,
1766
1864
  {
1767
1865
  type: "button",
1768
- "data-testid": "pde-ai-questionnaire-submit",
1769
- onClick: handleSubmit,
1770
- children: questionnaire.submitLabel ?? "Submit"
1866
+ "data-testid": "questionnaire-submit",
1867
+ disabled: isInteractionLocked,
1868
+ onClick: () => {
1869
+ void handleSubmit();
1870
+ },
1871
+ children: isSubmitting ? resolvedLabels.submitting : questionnaire.submitLabel ?? "Submit"
1771
1872
  }
1772
1873
  ) : null
1773
1874
  ] });
1774
1875
  };
1775
1876
  var getQuestionnaireStateKey = (questionnaire) => JSON.stringify([
1776
1877
  questionnaire.questionnaireId,
1777
- questionnaire.answers ?? {},
1778
- questionnaire.questions
1878
+ questionnaire.questions,
1879
+ questionnaire.status,
1880
+ questionnaire.statusMessage
1779
1881
  ]);
1780
- var PDEAIQuestionnaireCard = (props) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PDEAIQuestionnaireCardInner, { ...props }, getQuestionnaireStateKey(props.questionnaire));
1882
+ var QuestionnaireCard = (props) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(QuestionnaireCardInner, { ...props }, getQuestionnaireStateKey(props.questionnaire));
1781
1883
  var Card4 = import_styled4.default.section`
1782
1884
  display: grid;
1783
1885
  gap: 14px;
@@ -1925,6 +2027,10 @@ var ErrorMessage = import_styled4.default.div`
1925
2027
  color: rgba(255, 145, 145, 0.96);
1926
2028
  font-size: 12px;
1927
2029
  `;
2030
+ var SuccessMessage = import_styled4.default.div`
2031
+ color: rgba(164, 255, 210, 0.96);
2032
+ font-size: 12px;
2033
+ `;
1928
2034
  var SubmitButton = import_styled4.default.button`
1929
2035
  justify-self: flex-start;
1930
2036
  border: none;
@@ -1935,12 +2041,17 @@ var SubmitButton = import_styled4.default.button`
1935
2041
  font-weight: 700;
1936
2042
  padding: 10px 14px;
1937
2043
  cursor: pointer;
2044
+
2045
+ &:disabled {
2046
+ cursor: default;
2047
+ opacity: 0.72;
2048
+ }
1938
2049
  `;
1939
2050
 
1940
- // src/components/chat-thread/components/pde-ai-result-summary-card.tsx
2051
+ // src/components/chat-thread/components/result-summary-card.tsx
1941
2052
  var import_styled5 = __toESM(require("@emotion/styled"));
1942
2053
  var import_jsx_runtime6 = require("@emotion/react/jsx-runtime");
1943
- var PDEAIResultSummaryCard = ({ summary }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Card5, { "data-testid": "pde-ai-result-summary-card", "data-status": summary.status, children: [
2054
+ var ResultSummaryCard = ({ summary }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Card5, { "data-testid": "result-summary-card", "data-status": summary.status, children: [
1944
2055
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Eyebrow2, { children: summary.status }),
1945
2056
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Headline, { children: summary.headline }),
1946
2057
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Details, { children: summary.details.map((detail) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Detail, { children: detail }, detail)) })
@@ -2036,6 +2147,7 @@ var ImageViewer = ({ src, alt, onClose }) => {
2036
2147
  var import_jsx_runtime8 = require("@emotion/react/jsx-runtime");
2037
2148
  var MARKDOWN_REMARK_PLUGINS = [import_remark_gfm.default, import_remark_math.default];
2038
2149
  var MARKDOWN_REHYPE_PLUGINS = [import_rehype_katex.default];
2150
+ var USER_MESSAGE_COLLAPSE_HEIGHT_PX = 120;
2039
2151
  var MARKDOWN_COMPONENTS = {
2040
2152
  table: ({ node: _node, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TableWrapper, { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("table", { ...props }) })
2041
2153
  };
@@ -2048,10 +2160,101 @@ var renderMarkdownContent = (content) => /* @__PURE__ */ (0, import_jsx_runtime8
2048
2160
  children: content
2049
2161
  }
2050
2162
  );
2163
+ var renderPlainTextContent = (content) => content;
2164
+ var CollapseIcon = ({ expanded }) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2165
+ "svg",
2166
+ {
2167
+ "aria-hidden": "true",
2168
+ width: "16",
2169
+ height: "16",
2170
+ viewBox: "0 0 16 16",
2171
+ fill: "none",
2172
+ xmlns: "http://www.w3.org/2000/svg",
2173
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2174
+ "path",
2175
+ {
2176
+ d: "M4 6l4 4 4-4",
2177
+ stroke: "currentColor",
2178
+ strokeWidth: "1.6",
2179
+ strokeLinecap: "round",
2180
+ strokeLinejoin: "round",
2181
+ transform: expanded ? "rotate(180 8 8)" : void 0
2182
+ }
2183
+ )
2184
+ }
2185
+ );
2186
+ var useUserMessageCollapse = ({
2187
+ blocks,
2188
+ displayedBlocks,
2189
+ displayedContent,
2190
+ enabled,
2191
+ freshContent,
2192
+ settledContent
2193
+ }) => {
2194
+ const [isCollapsible, setIsCollapsible] = (0, import_react9.useState)(false);
2195
+ const [isExpanded, setIsExpanded] = (0, import_react9.useState)(false);
2196
+ const [bodyStackElement, setBodyStackElement] = (0, import_react9.useState)(null);
2197
+ const syncCollapseState = (0, import_react9.useCallback)(
2198
+ (element) => {
2199
+ const nextCollapsible = enabled && (element?.scrollHeight ?? 0) > USER_MESSAGE_COLLAPSE_HEIGHT_PX;
2200
+ setIsCollapsible(nextCollapsible);
2201
+ if (!nextCollapsible) {
2202
+ setIsExpanded(false);
2203
+ }
2204
+ },
2205
+ [enabled]
2206
+ );
2207
+ const bodyStackRef = (0, import_react9.useCallback)(
2208
+ (node) => {
2209
+ setBodyStackElement(node);
2210
+ syncCollapseState(node);
2211
+ },
2212
+ [syncCollapseState]
2213
+ );
2214
+ (0, import_react9.useLayoutEffect)(() => {
2215
+ if (!bodyStackElement) {
2216
+ return;
2217
+ }
2218
+ const frameId = requestAnimationFrame(() => {
2219
+ syncCollapseState(bodyStackElement);
2220
+ });
2221
+ return () => {
2222
+ cancelAnimationFrame(frameId);
2223
+ };
2224
+ }, [
2225
+ blocks,
2226
+ bodyStackElement,
2227
+ displayedBlocks,
2228
+ displayedContent,
2229
+ enabled,
2230
+ freshContent,
2231
+ settledContent,
2232
+ syncCollapseState
2233
+ ]);
2234
+ (0, import_react9.useLayoutEffect)(() => {
2235
+ if (!bodyStackElement || !enabled || typeof ResizeObserver === "undefined") {
2236
+ return;
2237
+ }
2238
+ const observer = new ResizeObserver(() => {
2239
+ syncCollapseState(bodyStackElement);
2240
+ });
2241
+ observer.observe(bodyStackElement);
2242
+ return () => {
2243
+ observer.disconnect();
2244
+ };
2245
+ }, [bodyStackElement, enabled, syncCollapseState]);
2246
+ return {
2247
+ bodyStackRef,
2248
+ isCollapsed: isCollapsible && !isExpanded,
2249
+ isCollapsible,
2250
+ isExpanded,
2251
+ toggleExpanded: () => setIsExpanded((current) => !current)
2252
+ };
2253
+ };
2051
2254
  var createExecutionConfirmationContent = (proposal) => [
2052
2255
  "Execution confirmed",
2053
- `- Equation: ${proposal.equationName}`,
2054
- ...proposal.solverName ? [`- Solver: ${proposal.solverName}`] : [],
2256
+ `- Equation: ${proposal.resourceName}`,
2257
+ ...proposal.executorName ? [`- Solver: ${proposal.executorName}`] : [],
2055
2258
  `- Proposal ID: ${proposal.proposalId}`
2056
2259
  ].join("\n");
2057
2260
  var areChatAttachmentsEqual = (previousAttachments, nextAttachments) => {
@@ -2073,10 +2276,10 @@ var areParameterSummaryItemsEqual = (previousItems, nextItems) => previousItems.
2073
2276
  const nextItem = nextItems[index];
2074
2277
  return item.label === nextItem?.label && item.value === nextItem.value && item.fieldPath === nextItem.fieldPath;
2075
2278
  });
2076
- 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(
2279
+ 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(
2077
2280
  (warning, index) => warning === nextProposal.warnings?.[index]
2078
2281
  );
2079
- 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]);
2282
+ 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]);
2080
2283
  var areStringArraysEqual = (previousValues, nextValues) => previousValues.length === nextValues.length && previousValues.every((value, index) => value === nextValues[index]);
2081
2284
  var areQuestionAnswersEqual = (previousAnswer, nextAnswer) => {
2082
2285
  if (Array.isArray(previousAnswer) || Array.isArray(nextAnswer)) {
@@ -2117,7 +2320,7 @@ var arePlanQuestionsEqual = (previousQuestion, nextQuestion) => {
2117
2320
  return false;
2118
2321
  }
2119
2322
  };
2120
- 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(
2323
+ 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(
2121
2324
  (question, index) => arePlanQuestionsEqual(question, nextQuestionnaire.questions[index])
2122
2325
  ) && areQuestionAnswerMapsEqual(previousQuestionnaire.answers, nextQuestionnaire.answers);
2123
2326
  var areMessageBlocksEqual = (previousBlocks, nextBlocks) => {
@@ -2198,6 +2401,8 @@ var ChatMessageItemView = ({
2198
2401
  const canSubmitConfirmation = isPlanMode && typeof onConfirmationSubmit === "function";
2199
2402
  const canSubmitQuestionnaire = isPlanMode && typeof onQuestionnaireSubmit === "function";
2200
2403
  const shouldShowStreamingCaret = isAssistantStreaming && (!shouldRenderStructuredBlocks || hasTextContent);
2404
+ const isUserMessage = message.role === "user";
2405
+ const messageRenderMode = isUserMessage ? "plain-text" : "markdown";
2201
2406
  const timelineConsumedText = messageRenderOrder === "timeline" ? getTimelineConsumedText(blocks) : "";
2202
2407
  const hasConsumedTimelineText = timelineConsumedText.length > 0 && displayedContent.startsWith(timelineConsumedText);
2203
2408
  const timelineDisplayedContent = hasConsumedTimelineText ? displayedContent.slice(timelineConsumedText.length) : displayedContent;
@@ -2214,6 +2419,21 @@ var ChatMessageItemView = ({
2214
2419
  message,
2215
2420
  messageRenderOrder
2216
2421
  });
2422
+ const {
2423
+ bodyStackRef,
2424
+ isCollapsed: isUserMessageCollapsed,
2425
+ isCollapsible: isUserMessageCollapsible,
2426
+ isExpanded: isUserMessageExpanded,
2427
+ toggleExpanded: toggleUserMessageExpanded
2428
+ } = useUserMessageCollapse({
2429
+ blocks,
2430
+ displayedBlocks,
2431
+ displayedContent,
2432
+ enabled: isUserMessage,
2433
+ freshContent,
2434
+ settledContent
2435
+ });
2436
+ const renderMessageContent = (content) => messageRenderMode === "plain-text" ? renderPlainTextContent(content) : renderMarkdownContent(content);
2217
2437
  const renderChatMessageBlock = (block, index) => {
2218
2438
  switch (block.type) {
2219
2439
  case "markdown":
@@ -2222,17 +2442,18 @@ var ChatMessageItemView = ({
2222
2442
  {
2223
2443
  "data-testid": `chat-message-block-${index}`,
2224
2444
  "data-block-tone": "settled",
2225
- children: renderMarkdownContent(block.text)
2445
+ "data-render-mode": messageRenderMode,
2446
+ children: renderMessageContent(block.text)
2226
2447
  },
2227
2448
  `markdown-${index}`
2228
2449
  );
2229
2450
  case "notice":
2230
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react9.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(PDEAINoticeCard, { text: block.text, tone: block.tone }) }, `notice-${index}`);
2451
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react9.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(NoticeCard, { text: block.text, tone: block.tone }) }, `notice-${index}`);
2231
2452
  case "parameter_summary":
2232
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react9.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(PDEAIParameterSummaryCard, { items: block.items }) }, `parameter-summary-${index}`);
2453
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react9.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ParameterSummaryCard, { items: block.items }) }, `parameter-summary-${index}`);
2233
2454
  case "confirmation_card":
2234
2455
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react9.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2235
- PDEAIExecutionConfirmationCard,
2456
+ ExecutionConfirmationCard,
2236
2457
  {
2237
2458
  proposal: block.proposal,
2238
2459
  interactive: isPlanMode,
@@ -2244,13 +2465,19 @@ var ChatMessageItemView = ({
2244
2465
  }
2245
2466
  ) }, `confirmation-card-${index}`);
2246
2467
  case "result_summary":
2247
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react9.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(PDEAIResultSummaryCard, { summary: block.summary }) }, `result-summary-${index}`);
2468
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react9.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ResultSummaryCard, { summary: block.summary }) }, `result-summary-${index}`);
2248
2469
  case "questionnaire":
2249
2470
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react9.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2250
- PDEAIQuestionnaireCard,
2471
+ QuestionnaireCard,
2251
2472
  {
2252
2473
  questionnaire: block.questionnaire,
2253
2474
  interactive: canSubmitQuestionnaire,
2475
+ labels: {
2476
+ submitting: labels.questionnaireSubmitting,
2477
+ submitted: labels.questionnaireSubmitted,
2478
+ validationPrefix: labels.questionnaireValidationPrefix,
2479
+ submitFailed: labels.questionnaireSubmitFailed
2480
+ },
2254
2481
  onSubmit: canSubmitQuestionnaire ? (submission) => onQuestionnaireSubmit({
2255
2482
  ...submission,
2256
2483
  sourceMessageId: message.id
@@ -2283,14 +2510,31 @@ var ChatMessageItemView = ({
2283
2510
  "data-testid": block.tone === "fresh" ? "chat-message-fresh-block" : "chat-message-settled-block",
2284
2511
  "data-block-tone": block.tone,
2285
2512
  "data-block-index": index,
2286
- children: renderMarkdownContent(block.content)
2513
+ "data-render-mode": messageRenderMode,
2514
+ children: renderMessageContent(block.content)
2287
2515
  },
2288
2516
  `${block.tone}-${index}`
2289
2517
  )),
2290
- !textBlocks.some((block) => block.content) && !settledText && !freshText && Boolean(textContent) ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ContentBlock, { "data-testid": "chat-message-settled-block", "data-block-tone": "settled", children: renderMarkdownContent(textContent) }) : null
2518
+ !textBlocks.some((block) => block.content) && !settledText && !freshText && Boolean(textContent) ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2519
+ ContentBlock,
2520
+ {
2521
+ "data-testid": "chat-message-settled-block",
2522
+ "data-block-tone": "settled",
2523
+ "data-render-mode": messageRenderMode,
2524
+ children: renderMessageContent(textContent)
2525
+ }
2526
+ ) : null
2291
2527
  ] });
2292
2528
  };
2293
- const renderStaticTextSegment = (content) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ContentBlock, { "data-testid": "chat-message-settled-block", "data-block-tone": "settled", children: renderMarkdownContent(content) });
2529
+ const renderStaticTextSegment = (content) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2530
+ ContentBlock,
2531
+ {
2532
+ "data-testid": "chat-message-settled-block",
2533
+ "data-block-tone": "settled",
2534
+ "data-render-mode": messageRenderMode,
2535
+ children: renderMessageContent(content)
2536
+ }
2537
+ );
2294
2538
  const bodySegments = (() => {
2295
2539
  if (!shouldRenderStructuredBlocks && hasTextContent) {
2296
2540
  return [{ type: "text" }];
@@ -2348,21 +2592,40 @@ var ChatMessageItemView = ({
2348
2592
  }
2349
2593
  ) : null,
2350
2594
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Role, { children: message.role === "user" ? labels.userRoleLabel : labels.assistantRoleLabel }),
2595
+ isUserMessageCollapsible ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2596
+ CollapseToggle,
2597
+ {
2598
+ type: "button",
2599
+ "data-testid": "chat-message-collapse-toggle",
2600
+ "aria-label": isUserMessageExpanded ? labels.collapseMessageAriaLabel : labels.expandMessageAriaLabel,
2601
+ "aria-expanded": isUserMessageExpanded,
2602
+ onClick: toggleUserMessageExpanded,
2603
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CollapseIcon, { expanded: isUserMessageExpanded })
2604
+ }
2605
+ ) : null,
2351
2606
  isStoppedAssistant ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(StatusTag, { "data-testid": "chat-message-stopped-tag", children: labels.stoppedResponse }) : null
2352
2607
  ] }),
2353
2608
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Content, { "data-testid": "chat-message-content", children: [
2354
- shouldRenderStructuredBlocks || hasTextContent ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ContentStack, { "data-testid": "chat-message-body-stack", children: bodySegments.map((segment, index) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2355
- ContentSegment,
2609
+ shouldRenderStructuredBlocks || hasTextContent ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2610
+ ContentStack,
2356
2611
  {
2357
- "data-testid": "chat-message-content-segment",
2358
- children: segment.type === "block" ? renderChatMessageBlock(segment.block, segment.index) : segment.type === "text" ? segment.content !== void 0 ? segment.useTimelineSegmentation ? renderTextContent({
2359
- content: segment.content,
2360
- displayedBlocks: segment.displayedBlocks,
2361
- useTimelineSegmentation: true
2362
- }) : renderStaticTextSegment(segment.content) : renderTextContent() : renderStaticTextSegment(segment.content)
2363
- },
2364
- segment.type === "text" ? `text-${index}` : segment.type === "markdown" ? `markdown-${index}` : `${segment.block.type}-${segment.index}`
2365
- )) }) : null,
2612
+ ref: bodyStackRef,
2613
+ "data-testid": "chat-message-body-stack",
2614
+ "data-collapsed": isUserMessageCollapsed,
2615
+ children: bodySegments.map((segment, index) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2616
+ ContentSegment,
2617
+ {
2618
+ "data-testid": "chat-message-content-segment",
2619
+ children: segment.type === "block" ? renderChatMessageBlock(segment.block, segment.index) : segment.type === "text" ? segment.content !== void 0 ? segment.useTimelineSegmentation ? renderTextContent({
2620
+ content: segment.content,
2621
+ displayedBlocks: segment.displayedBlocks,
2622
+ useTimelineSegmentation: true
2623
+ }) : renderStaticTextSegment(segment.content) : renderTextContent() : renderStaticTextSegment(segment.content)
2624
+ },
2625
+ segment.type === "text" ? `text-${index}` : segment.type === "markdown" ? `markdown-${index}` : `${segment.block.type}-${segment.index}`
2626
+ ))
2627
+ }
2628
+ ) : null,
2366
2629
  attachments.length ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(AttachmentGrid, { "data-testid": "chat-message-attachment-grid", children: attachments.map((attachment) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2367
2630
  AttachmentButton,
2368
2631
  {
@@ -2447,6 +2710,29 @@ var StatusTag = import_styled7.default.span`
2447
2710
  letter-spacing: 0.02em;
2448
2711
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
2449
2712
  `;
2713
+ var CollapseToggle = import_styled7.default.button`
2714
+ margin-left: auto;
2715
+ width: 28px;
2716
+ height: 28px;
2717
+ display: inline-flex;
2718
+ align-items: center;
2719
+ justify-content: center;
2720
+ border: 1px solid rgba(255, 255, 255, 0.12);
2721
+ border-radius: 999px;
2722
+ background: rgba(255, 255, 255, 0.06);
2723
+ color: rgba(255, 255, 255, 0.72);
2724
+ cursor: pointer;
2725
+ transition:
2726
+ background 160ms ease-out,
2727
+ border-color 160ms ease-out,
2728
+ color 160ms ease-out;
2729
+
2730
+ &:hover {
2731
+ background: rgba(255, 255, 255, 0.1);
2732
+ border-color: rgba(255, 255, 255, 0.18);
2733
+ color: rgba(255, 255, 255, 0.92);
2734
+ }
2735
+ `;
2450
2736
  var Content = import_styled7.default.div`
2451
2737
  color: rgba(255, 255, 255, 0.92);
2452
2738
  line-height: 1.6;
@@ -2491,6 +2777,11 @@ var ContentStack = import_styled7.default.div`
2491
2777
  display: flex;
2492
2778
  flex-direction: column;
2493
2779
  gap: 16px;
2780
+
2781
+ &[data-collapsed='true'] {
2782
+ max-height: ${USER_MESSAGE_COLLAPSE_HEIGHT_PX}px;
2783
+ overflow: hidden;
2784
+ }
2494
2785
  `;
2495
2786
  var ContentSegment = import_styled7.default.div`
2496
2787
  min-width: 0;
@@ -2504,6 +2795,11 @@ var ContentBlock = import_styled7.default.div`
2504
2795
  margin-top: 16px;
2505
2796
  }
2506
2797
 
2798
+ &[data-render-mode='plain-text'] {
2799
+ white-space: pre-wrap;
2800
+ overflow-wrap: anywhere;
2801
+ }
2802
+
2507
2803
  &[data-block-tone='fresh'] {
2508
2804
  opacity: 0.85;
2509
2805
  filter: brightness(0.82) saturate(0.88);
@@ -2911,7 +3207,14 @@ var ChatThread = () => {
2911
3207
  const error = useChatStore((s) => s.errorBySession[s.activeSessionId ?? ""]);
2912
3208
  const updateQA = useChatStore((s) => s.updateQuestionnaireAnswers);
2913
3209
  const clearSessionError = useChatStore((s) => s.clearSessionError);
2914
- const { sendRef, retryRef, renderMessageBlock, labels } = useChatContext();
3210
+ const {
3211
+ sendRef,
3212
+ retryRef,
3213
+ renderMessageBlock,
3214
+ handleQuestionnaireSubmit: customQuestionnaireSubmit,
3215
+ handleConfirmationSubmit: customConfirmationSubmit,
3216
+ labels
3217
+ } = useChatContext();
2915
3218
  const handleRetry = (0, import_react11.useCallback)(() => {
2916
3219
  if (!activeSessionId)
2917
3220
  return;
@@ -2919,7 +3222,25 @@ var ChatThread = () => {
2919
3222
  void retryRef.current();
2920
3223
  }, [activeSessionId, clearSessionError, retryRef]);
2921
3224
  const handleQuestionnaireSubmit = (0, import_react11.useCallback)(
2922
- (submission) => {
3225
+ async (submission) => {
3226
+ if (customQuestionnaireSubmit) {
3227
+ const handled = await customQuestionnaireSubmit(submission, {
3228
+ sessionId: activeSessionId ?? void 0,
3229
+ mode: activeSessionMode
3230
+ });
3231
+ if (handled !== false) {
3232
+ if (activeSessionId && submission.sourceMessageId) {
3233
+ updateQA(
3234
+ activeSessionId,
3235
+ submission.sourceMessageId,
3236
+ submission.questionnaireId,
3237
+ submission.answers
3238
+ );
3239
+ }
3240
+ return;
3241
+ }
3242
+ }
3243
+ await sendRef.current(submission.content);
2923
3244
  if (activeSessionId && submission.sourceMessageId) {
2924
3245
  updateQA(
2925
3246
  activeSessionId,
@@ -2928,15 +3249,23 @@ var ChatThread = () => {
2928
3249
  submission.answers
2929
3250
  );
2930
3251
  }
2931
- void sendRef.current(submission.content);
2932
3252
  },
2933
- [activeSessionId, updateQA, sendRef]
3253
+ [activeSessionId, activeSessionMode, updateQA, sendRef, customQuestionnaireSubmit]
2934
3254
  );
2935
- const handleConfirmationSubmit = (0, import_react11.useCallback)(
2936
- (submission) => {
2937
- void sendRef.current(submission.content);
3255
+ const handleConfirmation = (0, import_react11.useCallback)(
3256
+ async (submission) => {
3257
+ if (customConfirmationSubmit) {
3258
+ const handled = await customConfirmationSubmit(submission, {
3259
+ sessionId: activeSessionId ?? void 0,
3260
+ mode: activeSessionMode
3261
+ });
3262
+ if (handled !== false) {
3263
+ return;
3264
+ }
3265
+ }
3266
+ await sendRef.current(submission.content);
2938
3267
  },
2939
- [sendRef]
3268
+ [activeSessionId, activeSessionMode, sendRef, customConfirmationSubmit]
2940
3269
  );
2941
3270
  if (!hasSessions || messages.length === 0 && !streamingMessage) {
2942
3271
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ChatThreadEmptyState, {});
@@ -2950,7 +3279,7 @@ var ChatThread = () => {
2950
3279
  error,
2951
3280
  retryButtonLabel: labels.retryButton,
2952
3281
  onRetry: handleRetry,
2953
- onConfirmationSubmit: handleConfirmationSubmit,
3282
+ onConfirmationSubmit: handleConfirmation,
2954
3283
  onQuestionnaireSubmit: handleQuestionnaireSubmit,
2955
3284
  renderMessageBlock
2956
3285
  }
@@ -2964,6 +3293,7 @@ var Container = import_styled9.default.div`
2964
3293
  min-height: 0;
2965
3294
  overflow: auto;
2966
3295
  padding: 24px;
3296
+ margin-bottom: 24px;
2967
3297
  overscroll-behavior: contain;
2968
3298
 
2969
3299
  &::-webkit-scrollbar {
@@ -4043,6 +4373,27 @@ var StopSpinner = import_styled13.default.span`
4043
4373
 
4044
4374
  // src/components/chat-composer/index.tsx
4045
4375
  var import_jsx_runtime15 = require("@emotion/react/jsx-runtime");
4376
+ var CHAT_COMPOSER_LINE_HEIGHT_PX = 20;
4377
+ var CHAT_COMPOSER_MAX_ROWS = 7;
4378
+ var CHAT_COMPOSER_PADDING_TOP_PX = 8;
4379
+ var CHAT_COMPOSER_PADDING_BOTTOM_PX = 12;
4380
+ var CHAT_COMPOSER_PADDING_BLOCK_PX = CHAT_COMPOSER_PADDING_TOP_PX + CHAT_COMPOSER_PADDING_BOTTOM_PX;
4381
+ var CHAT_COMPOSER_MIN_ROWS = 4;
4382
+ var CHAT_COMPOSER_EXPANDED_MAX_ROWS = 60;
4383
+ var CHAT_COMPOSER_EXPANDED_MAX_VIEWPORT_RATIO = 0.7;
4384
+ var CHAT_COMPOSER_EXPANDED_RESERVED_SPACE_PX = 96;
4385
+ var CHAT_COMPOSER_MAX_HEIGHT_PX = CHAT_COMPOSER_MAX_ROWS * CHAT_COMPOSER_LINE_HEIGHT_PX + CHAT_COMPOSER_PADDING_BLOCK_PX;
4386
+ var CHAT_COMPOSER_EXPANDED_ROWS_HEIGHT_PX = CHAT_COMPOSER_EXPANDED_MAX_ROWS * CHAT_COMPOSER_LINE_HEIGHT_PX + CHAT_COMPOSER_PADDING_BLOCK_PX;
4387
+ var getExpandedComposerHeightPx = () => {
4388
+ if (typeof window === "undefined") {
4389
+ return CHAT_COMPOSER_EXPANDED_ROWS_HEIGHT_PX;
4390
+ }
4391
+ const viewportLimitedHeight = window.innerHeight * CHAT_COMPOSER_EXPANDED_MAX_VIEWPORT_RATIO - CHAT_COMPOSER_EXPANDED_RESERVED_SPACE_PX;
4392
+ return Math.max(
4393
+ CHAT_COMPOSER_MAX_HEIGHT_PX,
4394
+ Math.floor(Math.min(CHAT_COMPOSER_EXPANDED_ROWS_HEIGHT_PX, viewportLimitedHeight))
4395
+ );
4396
+ };
4046
4397
  var PlusIcon = () => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
4047
4398
  "svg",
4048
4399
  {
@@ -4055,6 +4406,36 @@ var PlusIcon = () => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
4055
4406
  children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("path", { d: "M8 3v10M3 8h10", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round" })
4056
4407
  }
4057
4408
  );
4409
+ var ComposerExpandIcon = ({ expanded }) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
4410
+ "svg",
4411
+ {
4412
+ "aria-hidden": "true",
4413
+ width: "16",
4414
+ height: "16",
4415
+ viewBox: "0 0 16 16",
4416
+ fill: "none",
4417
+ xmlns: "http://www.w3.org/2000/svg",
4418
+ children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
4419
+ "path",
4420
+ {
4421
+ d: "M14 6h-4V2M10 6l4-4M2 10h4v4M6 10l-4 4",
4422
+ stroke: "currentColor",
4423
+ strokeWidth: "1.5",
4424
+ strokeLinecap: "round",
4425
+ strokeLinejoin: "round"
4426
+ }
4427
+ ) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
4428
+ "path",
4429
+ {
4430
+ d: "M10 2h4v4M14 2L9 7M6 14H2v-4M2 14l5-5",
4431
+ stroke: "currentColor",
4432
+ strokeWidth: "1.5",
4433
+ strokeLinecap: "round",
4434
+ strokeLinejoin: "round"
4435
+ }
4436
+ )
4437
+ }
4438
+ );
4058
4439
  var ChatComposerView = ({
4059
4440
  value,
4060
4441
  placeholder,
@@ -4071,6 +4452,8 @@ var ChatComposerView = ({
4071
4452
  isStopping,
4072
4453
  enableImageAttachments,
4073
4454
  modeLabels,
4455
+ expandComposerAriaLabel,
4456
+ collapseComposerAriaLabel,
4074
4457
  onValueChange,
4075
4458
  onPickImages,
4076
4459
  onPasteImages,
@@ -4082,6 +4465,9 @@ var ChatComposerView = ({
4082
4465
  onSend
4083
4466
  }) => {
4084
4467
  const imageInputRef = (0, import_react15.useRef)(null);
4468
+ const inputRef = (0, import_react15.useRef)(null);
4469
+ const [isComposerExpandable, setIsComposerExpandable] = (0, import_react15.useState)(false);
4470
+ const [isComposerExpanded, setIsComposerExpanded] = (0, import_react15.useState)(false);
4085
4471
  const canSend = canSendChatMessage({
4086
4472
  value,
4087
4473
  attachmentCount: attachments.length,
@@ -4089,6 +4475,26 @@ var ChatComposerView = ({
4089
4475
  isModelsError,
4090
4476
  hasModels
4091
4477
  });
4478
+ (0, import_react15.useLayoutEffect)(() => {
4479
+ const element = inputRef.current;
4480
+ if (!element) {
4481
+ return;
4482
+ }
4483
+ if (!isComposerExpanded) {
4484
+ element.style.height = "0px";
4485
+ }
4486
+ const scrollHeight = element.scrollHeight;
4487
+ const nextExpandable = scrollHeight > CHAT_COMPOSER_MAX_HEIGHT_PX;
4488
+ const expandedHeight = getExpandedComposerHeightPx();
4489
+ const nextHeight = isComposerExpanded ? expandedHeight : Math.min(scrollHeight, CHAT_COMPOSER_MAX_HEIGHT_PX);
4490
+ setIsComposerExpandable(nextExpandable);
4491
+ element.style.height = `${nextHeight}px`;
4492
+ element.style.overflowY = !isComposerExpanded && scrollHeight > CHAT_COMPOSER_MAX_HEIGHT_PX ? "auto" : "hidden";
4493
+ }, [isComposerExpanded, value]);
4494
+ const handleSend = async () => {
4495
+ setIsComposerExpanded(false);
4496
+ await onSend();
4497
+ };
4092
4498
  const handleKeyDown = (event) => {
4093
4499
  if (shouldStopChatComposer({ key: event.key, shiftKey: event.shiftKey, isStreaming })) {
4094
4500
  event.preventDefault();
@@ -4098,7 +4504,7 @@ var ChatComposerView = ({
4098
4504
  if (!shouldSubmitChatComposer({ key: event.key, shiftKey: event.shiftKey, canSend }))
4099
4505
  return;
4100
4506
  event.preventDefault();
4101
- void onSend();
4507
+ void handleSend();
4102
4508
  };
4103
4509
  const handlePickImages = (event) => {
4104
4510
  if (event.target.files?.length)
@@ -4133,60 +4539,77 @@ var ChatComposerView = ({
4133
4539
  }
4134
4540
  ),
4135
4541
  attachmentNotice === "limit_reached" ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(AttachmentNotice, { "data-testid": "chat-composer-attachment-notice", children: attachmentLimitNotice }) : null,
4136
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
4137
- Input,
4138
- {
4139
- "data-testid": "chat-composer-input",
4140
- value,
4141
- onChange: (event) => onValueChange(event.target.value),
4142
- onKeyDown: handleKeyDown,
4143
- onPaste: enableImageAttachments ? handlePaste : void 0,
4144
- placeholder
4145
- }
4146
- ),
4147
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Footer, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Actions2, { "data-testid": "chat-composer-actions", children: [
4148
- enableImageAttachments ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
4149
- AttachButton,
4542
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(InputArea, { "data-testid": "chat-composer-input-area", children: [
4543
+ isComposerExpanded || isComposerExpandable ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
4544
+ ComposerExpandButton,
4150
4545
  {
4151
4546
  type: "button",
4152
- "data-testid": "chat-composer-attach-image",
4153
- "aria-label": "Attach image",
4154
- onClick: () => imageInputRef.current?.click(),
4155
- children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(PlusIcon, {})
4547
+ "data-testid": "chat-composer-expand-toggle",
4548
+ "aria-label": isComposerExpanded ? collapseComposerAriaLabel : expandComposerAriaLabel,
4549
+ "aria-expanded": isComposerExpanded,
4550
+ onClick: () => setIsComposerExpanded((current) => !current),
4551
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ComposerExpandIcon, { expanded: isComposerExpanded })
4156
4552
  }
4157
4553
  ) : null,
4158
4554
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
4159
- ChatModeControl,
4160
- {
4161
- value: selectedMode,
4162
- disabled: isStreaming,
4163
- labels: modeLabels,
4164
- onChange: onSelectedModeChange
4165
- }
4166
- ),
4167
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
4168
- ChatModelControl,
4555
+ Input,
4169
4556
  {
4170
- selectedModel,
4171
- availableModels,
4172
- isModelsLoading,
4173
- isModelsError,
4174
- hasModels,
4175
- onSelectedModelChange,
4176
- onReloadModels
4557
+ ref: inputRef,
4558
+ "data-testid": "chat-composer-input",
4559
+ "data-expanded": isComposerExpanded,
4560
+ value,
4561
+ onChange: (event) => onValueChange(event.target.value),
4562
+ onKeyDown: handleKeyDown,
4563
+ onPaste: enableImageAttachments ? handlePaste : void 0,
4564
+ placeholder
4177
4565
  }
4178
- ),
4179
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
4180
- ChatSendActions,
4566
+ )
4567
+ ] }),
4568
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Footer, { children: [
4569
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(LeadingActions, { "data-testid": "chat-composer-leading-actions", children: enableImageAttachments ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
4570
+ AttachButton,
4181
4571
  {
4182
- canSend,
4183
- isStreaming,
4184
- isStopping,
4185
- onStop,
4186
- onSend
4572
+ type: "button",
4573
+ "data-testid": "chat-composer-attach-image",
4574
+ "aria-label": "Attach image",
4575
+ onClick: () => imageInputRef.current?.click(),
4576
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(PlusIcon, {})
4187
4577
  }
4188
- )
4189
- ] }) })
4578
+ ) : null }),
4579
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(TrailingActions, { "data-testid": "chat-composer-trailing-actions", children: [
4580
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
4581
+ ChatModeControl,
4582
+ {
4583
+ value: selectedMode,
4584
+ disabled: isStreaming,
4585
+ labels: modeLabels,
4586
+ onChange: onSelectedModeChange
4587
+ }
4588
+ ),
4589
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
4590
+ ChatModelControl,
4591
+ {
4592
+ selectedModel,
4593
+ availableModels,
4594
+ isModelsLoading,
4595
+ isModelsError,
4596
+ hasModels,
4597
+ onSelectedModelChange,
4598
+ onReloadModels
4599
+ }
4600
+ ),
4601
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
4602
+ ChatSendActions,
4603
+ {
4604
+ canSend,
4605
+ isStreaming,
4606
+ isStopping,
4607
+ onStop,
4608
+ onSend: handleSend
4609
+ }
4610
+ )
4611
+ ] })
4612
+ ] })
4190
4613
  ] }) });
4191
4614
  };
4192
4615
  var ChatComposer = () => {
@@ -4222,6 +4645,8 @@ var ChatComposer = () => {
4222
4645
  isStopping: state.isStopping,
4223
4646
  enableImageAttachments,
4224
4647
  modeLabels,
4648
+ expandComposerAriaLabel: labels.expandComposerAriaLabel,
4649
+ collapseComposerAriaLabel: labels.collapseComposerAriaLabel,
4225
4650
  onValueChange: actions.setValue,
4226
4651
  onPickImages: actions.pickImages,
4227
4652
  onPasteImages: actions.pasteImages,
@@ -4238,6 +4663,16 @@ var Container2 = import_styled14.default.div`
4238
4663
  padding: 0 16px 16px;
4239
4664
  `;
4240
4665
  var Surface = import_styled14.default.div`
4666
+ display: grid;
4667
+ grid-template-columns: minmax(0, 1fr);
4668
+ grid-template-areas:
4669
+ 'attachments'
4670
+ 'notice'
4671
+ 'input'
4672
+ 'footer';
4673
+ width: 100%;
4674
+ max-width: 760px;
4675
+ margin: 0 auto;
4241
4676
  background: var(--border-color);
4242
4677
  border-radius: 20px;
4243
4678
  border: 1px solid var(--border-hover);
@@ -4247,6 +4682,7 @@ var Surface = import_styled14.default.div`
4247
4682
  backdrop-filter: blur(10px);
4248
4683
  `;
4249
4684
  var AttachmentNotice = import_styled14.default.div`
4685
+ grid-area: notice;
4250
4686
  margin: 10px 12px 0;
4251
4687
  padding: 8px 10px;
4252
4688
  border-radius: 10px;
@@ -4256,19 +4692,45 @@ var AttachmentNotice = import_styled14.default.div`
4256
4692
  font-size: 12px;
4257
4693
  line-height: 1.4;
4258
4694
  `;
4695
+ var InputArea = import_styled14.default.div`
4696
+ grid-area: input;
4697
+ position: relative;
4698
+ `;
4259
4699
  var Input = import_styled14.default.textarea`
4700
+ --textarea-line-height: ${CHAT_COMPOSER_LINE_HEIGHT_PX}px;
4701
+ --textarea-min-rows: ${CHAT_COMPOSER_MIN_ROWS};
4702
+ --textarea-max-rows: ${CHAT_COMPOSER_MAX_ROWS};
4703
+ --textarea-expanded-max-rows: ${CHAT_COMPOSER_EXPANDED_MAX_ROWS};
4704
+ --textarea-padding-top: ${CHAT_COMPOSER_PADDING_TOP_PX}px;
4705
+ --textarea-padding-bottom: ${CHAT_COMPOSER_PADDING_BOTTOM_PX}px;
4706
+ --textarea-padding-block: calc(var(--textarea-padding-top) + var(--textarea-padding-bottom));
4707
+ --textarea-max-height: calc(
4708
+ var(--textarea-max-rows) * var(--textarea-line-height) + var(--textarea-padding-block)
4709
+ );
4710
+ --textarea-expanded-max-height: min(
4711
+ calc(
4712
+ var(--textarea-expanded-max-rows) * var(--textarea-line-height) +
4713
+ var(--textarea-padding-block)
4714
+ ),
4715
+ calc(70vh - ${CHAT_COMPOSER_EXPANDED_RESERVED_SPACE_PX}px)
4716
+ );
4260
4717
  width: 100%;
4261
- min-height: 96px;
4718
+ min-height: calc(
4719
+ var(--textarea-min-rows) * var(--textarea-line-height) + var(--textarea-padding-block)
4720
+ );
4721
+ max-height: var(--textarea-max-height);
4722
+ box-sizing: border-box;
4262
4723
  resize: none;
4263
4724
  appearance: none;
4264
4725
  border: 0;
4265
4726
  outline: 0;
4266
4727
  background: transparent;
4267
- padding: 8px 12px 12px 12px;
4728
+ padding: var(--textarea-padding-top) 44px var(--textarea-padding-bottom) 12px;
4268
4729
  font-weight: 400;
4269
4730
  font-size: 14px;
4270
4731
  color: var(--text-primary);
4271
- line-height: 20px;
4732
+ line-height: var(--textarea-line-height);
4733
+ overflow-y: hidden;
4272
4734
 
4273
4735
  &::placeholder {
4274
4736
  color: var(--text-secondary);
@@ -4277,19 +4739,50 @@ var Input = import_styled14.default.textarea`
4277
4739
  &::-webkit-resizer {
4278
4740
  display: none;
4279
4741
  }
4742
+
4743
+ &[data-expanded='true'] {
4744
+ max-height: var(--textarea-expanded-max-height);
4745
+ }
4746
+ `;
4747
+ var ComposerExpandButton = import_styled14.default.button`
4748
+ position: absolute;
4749
+ top: 8px;
4750
+ right: 10px;
4751
+ width: 28px;
4752
+ height: 28px;
4753
+ display: grid;
4754
+ place-items: center;
4755
+ border: none;
4756
+ border-radius: 999px;
4757
+ background: transparent;
4758
+ color: rgba(255, 255, 255, 0.72);
4759
+ cursor: pointer;
4760
+ z-index: 1;
4761
+
4762
+ &:hover {
4763
+ background: rgba(255, 255, 255, 0.08);
4764
+ color: rgba(255, 255, 255, 0.92);
4765
+ }
4280
4766
  `;
4281
4767
  var Footer = import_styled14.default.div`
4282
- display: flex;
4768
+ grid-area: footer;
4769
+ display: grid;
4770
+ grid-template-columns: minmax(0, 1fr) auto;
4283
4771
  align-items: flex-end;
4284
- justify-content: stretch;
4285
4772
  gap: 16px;
4286
4773
  padding: 0 14px 14px;
4287
4774
  `;
4288
- var Actions2 = import_styled14.default.div`
4775
+ var LeadingActions = import_styled14.default.div`
4776
+ display: flex;
4777
+ align-items: center;
4778
+ justify-content: flex-start;
4779
+ gap: 8px;
4780
+ min-width: 0;
4781
+ `;
4782
+ var TrailingActions = import_styled14.default.div`
4289
4783
  display: flex;
4290
4784
  align-items: center;
4291
4785
  flex-wrap: wrap;
4292
- width: 100%;
4293
4786
  min-width: 0;
4294
4787
  justify-content: flex-end;
4295
4788
  gap: 8px;