@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.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,40 @@ 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 === "custom" && incomingBlock.blockKey) {
65
+ const mergePolicy = incomingBlock.mergePolicy ?? "append";
66
+ if (mergePolicy !== "append") {
67
+ const existingIndex2 = nextBlocks.findIndex(
68
+ (block) => block.type === "custom" && block.blockKey === incomingBlock.blockKey
69
+ );
70
+ if (existingIndex2 !== -1) {
71
+ if (mergePolicy === "replace") {
72
+ nextBlocks[existingIndex2] = incomingBlock;
73
+ }
74
+ return;
75
+ }
76
+ }
77
+ nextBlocks.push(incomingBlock);
78
+ return;
79
+ }
80
+ if (incomingBlock.type !== "questionnaire") {
81
+ nextBlocks.push(incomingBlock);
82
+ return;
83
+ }
84
+ const existingIndex = nextBlocks.findIndex(
85
+ (block) => block.type === "questionnaire" && block.questionnaire.questionnaireId === incomingBlock.questionnaire.questionnaireId
86
+ );
87
+ if (existingIndex === -1) {
88
+ nextBlocks.push(incomingBlock);
89
+ return;
90
+ }
91
+ nextBlocks[existingIndex] = incomingBlock;
92
+ });
93
+ return nextBlocks;
94
+ };
53
95
  var finalizeStreamingMessage = (state, sessionId, status, clearError = false) => {
54
96
  const message = state.streamingMessageBySession[sessionId];
55
97
  const hasRenderableMessagePayload = Boolean(
@@ -239,10 +281,15 @@ var createChatStore = (initialState) => createStore((set, get) => ({
239
281
  const target = state.streamingMessageBySession[sessionId];
240
282
  if (!target)
241
283
  return;
284
+ const nextBlocks = patch.blocks !== void 0 ? mergeStreamingBlocks(target.blocks, patch.blocks) : target.blocks;
242
285
  set({
243
286
  streamingMessageBySession: {
244
287
  ...state.streamingMessageBySession,
245
- [sessionId]: { ...target, ...patch }
288
+ [sessionId]: {
289
+ ...target,
290
+ ...patch,
291
+ ...patch.blocks !== void 0 ? { blocks: nextBlocks } : {}
292
+ }
246
293
  }
247
294
  });
248
295
  },
@@ -462,9 +509,27 @@ var DEFAULT_CHAT_TRANSPORT_ENDPOINTS = {
462
509
  completions: "/chat/completions",
463
510
  terminate: "/chat/terminate"
464
511
  };
512
+ var createToolExecutionHeaders = (policy) => {
513
+ if (!policy?.enabled) {
514
+ return {};
515
+ }
516
+ return {
517
+ "X-Tool-Approval-Required": String(Boolean(policy.approvalRequired)),
518
+ ...typeof policy.approvalTimeoutSec === "number" ? { "X-Tool-Approval-Timeout": String(policy.approvalTimeoutSec) } : {}
519
+ };
520
+ };
521
+ var createModeDefaultHeaders = (mode) => {
522
+ if (mode === "ask" || mode === "plan") {
523
+ return {
524
+ "X-Tool-Approval-Required": "false"
525
+ };
526
+ }
527
+ return {};
528
+ };
465
529
  var createDefaultChatTransport = ({
466
530
  apiBaseUrl,
467
531
  authToken,
532
+ toolExecutionPolicy,
468
533
  streamHeaders,
469
534
  transformStreamPacket,
470
535
  endpoints,
@@ -475,6 +540,10 @@ var createDefaultChatTransport = ({
475
540
  ...DEFAULT_CHAT_TRANSPORT_ENDPOINTS,
476
541
  ...endpoints
477
542
  };
543
+ const resolvedStreamHeaders = {
544
+ ...createToolExecutionHeaders(toolExecutionPolicy),
545
+ ...streamHeaders
546
+ };
478
547
  return {
479
548
  getModels: () => getChatModels(client, resolvedEndpoints.models),
480
549
  startStream: async ({
@@ -488,12 +557,16 @@ var createDefaultChatTransport = ({
488
557
  onDone,
489
558
  onError
490
559
  }) => {
560
+ const requestHeaders = {
561
+ ...createModeDefaultHeaders(mode),
562
+ ...resolvedStreamHeaders
563
+ };
491
564
  await startChatStream({
492
565
  apiBaseUrl,
493
566
  endpointPath: resolvedEndpoints.completions,
494
567
  sessionId,
495
568
  authToken,
496
- requestHeaders: streamHeaders,
569
+ requestHeaders,
497
570
  model,
498
571
  mode,
499
572
  content,
@@ -522,6 +595,8 @@ var AiChatProvider = (props) => {
522
595
  defaultMode,
523
596
  labels,
524
597
  renderMessageBlock,
598
+ handleQuestionnaireSubmit,
599
+ handleConfirmationSubmit,
525
600
  messageRenderOrder,
526
601
  enableImageAttachments = true,
527
602
  children
@@ -577,6 +652,8 @@ var AiChatProvider = (props) => {
577
652
  sendRef,
578
653
  retryRef,
579
654
  renderMessageBlock,
655
+ handleQuestionnaireSubmit,
656
+ handleConfirmationSubmit,
580
657
  messageRenderOrder,
581
658
  transformStreamPacket: defaultTransformStreamPacket,
582
659
  enableImageAttachments
@@ -587,6 +664,8 @@ var AiChatProvider = (props) => {
587
664
  defaultAuthToken,
588
665
  defaultTransformStreamPacket,
589
666
  enableImageAttachments,
667
+ handleConfirmationSubmit,
668
+ handleQuestionnaireSubmit,
590
669
  labels,
591
670
  messageRenderOrder,
592
671
  renderMessageBlock,
@@ -600,7 +679,7 @@ var AiChatProvider = (props) => {
600
679
  };
601
680
 
602
681
  // src/components/chat-thread/index.tsx
603
- import { useCallback as useCallback2, useLayoutEffect, useMemo as useMemo4, useRef as useRef4, useState as useState4 } from "react";
682
+ import { useCallback as useCallback3, useLayoutEffect as useLayoutEffect2, useMemo as useMemo4, useRef as useRef4, useState as useState4 } from "react";
604
683
  import styled9 from "@emotion/styled";
605
684
 
606
685
  // src/context/use-chat-context.ts
@@ -621,7 +700,7 @@ var useChatStore = (selector) => {
621
700
  var CHAT_THREAD_SCROLL_TOP_GAP = 16;
622
701
 
623
702
  // src/components/chat-thread/components/chat-message-item.tsx
624
- import { Fragment, memo, useState as useState3 } from "react";
703
+ import { Fragment, memo, useCallback as useCallback2, useLayoutEffect, useState as useState3 } from "react";
625
704
  import styled7 from "@emotion/styled";
626
705
  import { keyframes } from "@emotion/react";
627
706
  import ReactMarkdown from "react-markdown";
@@ -896,11 +975,11 @@ var getTimelineBlockKey = (block, index) => {
896
975
  case "confirmation_card":
897
976
  return `${index}:confirmation_card:${block.proposal.proposalId}`;
898
977
  case "result_summary":
899
- return `${index}:result_summary:${block.summary.taskId}:${block.summary.status}`;
978
+ return `${index}:result_summary:${block.summary.summaryId}:${block.summary.status}`;
900
979
  case "questionnaire":
901
980
  return `${index}:questionnaire:${block.questionnaire.questionnaireId}`;
902
981
  case "custom":
903
- return `${index}:custom:${block.kind}:${stringifyTimelineKeyPart(block.data)}`;
982
+ return block.blockKey ? `custom:${block.blockKey}` : `${index}:custom:${block.kind}:${stringifyTimelineKeyPart(block.data)}`;
904
983
  default:
905
984
  return null;
906
985
  }
@@ -1188,21 +1267,21 @@ var useTimelineBlockAnchors = ({
1188
1267
  };
1189
1268
  };
1190
1269
 
1191
- // src/components/chat-thread/components/pde-ai-execution-confirmation-card.tsx
1270
+ // src/components/chat-thread/components/execution-confirmation-card.tsx
1192
1271
  import styled from "@emotion/styled";
1193
1272
  import { jsx as jsx2, jsxs } from "@emotion/react/jsx-runtime";
1194
- var PDEAIExecutionConfirmationCard = ({
1273
+ var ExecutionConfirmationCard = ({
1195
1274
  proposal,
1196
1275
  interactive = false,
1197
1276
  onApply,
1198
1277
  onConfirm,
1199
1278
  onRevise
1200
1279
  }) => {
1201
- return /* @__PURE__ */ jsxs(Card, { "data-testid": "pde-ai-execution-confirmation-card", children: [
1280
+ return /* @__PURE__ */ jsxs(Card, { "data-testid": "execution-confirmation-card", children: [
1202
1281
  /* @__PURE__ */ jsxs(Header, { children: [
1203
1282
  /* @__PURE__ */ jsx2(Eyebrow, { children: "Execution Proposal" }),
1204
- /* @__PURE__ */ jsx2(Title, { children: proposal.equationName }),
1205
- proposal.solverName ? /* @__PURE__ */ jsx2(Subtitle, { children: proposal.solverName }) : null
1283
+ /* @__PURE__ */ jsx2(Title, { children: proposal.resourceName }),
1284
+ proposal.executorName ? /* @__PURE__ */ jsx2(Subtitle, { children: proposal.executorName }) : null
1206
1285
  ] }),
1207
1286
  /* @__PURE__ */ jsx2(SummaryList, { children: proposal.parameterSummary.map((item) => /* @__PURE__ */ jsxs(SummaryItem, { children: [
1208
1287
  /* @__PURE__ */ jsx2(SummaryLabel, { children: item.label }),
@@ -1210,21 +1289,13 @@ var PDEAIExecutionConfirmationCard = ({
1210
1289
  ] }, `${item.fieldPath ?? item.label}-${item.value}`)) }),
1211
1290
  proposal.warnings?.length ? /* @__PURE__ */ jsx2(Warnings, { children: proposal.warnings.map((warning) => /* @__PURE__ */ jsx2(Warning, { children: warning }, warning)) }) : null,
1212
1291
  interactive ? /* @__PURE__ */ jsxs(Actions, { children: [
1213
- onApply && /* @__PURE__ */ jsx2(ActionButton, { type: "button", "data-testid": "pde-ai-confirmation-apply", onClick: onApply, children: "Apply to Parameters" }),
1214
- onConfirm && /* @__PURE__ */ jsx2(
1215
- ActionButton,
1216
- {
1217
- type: "button",
1218
- "data-testid": "pde-ai-confirmation-confirm",
1219
- onClick: onConfirm,
1220
- children: "Confirm Execution"
1221
- }
1222
- ),
1292
+ onApply && /* @__PURE__ */ jsx2(ActionButton, { type: "button", "data-testid": "confirmation-apply", onClick: onApply, children: "Apply to Parameters" }),
1293
+ onConfirm && /* @__PURE__ */ jsx2(ActionButton, { type: "button", "data-testid": "confirmation-confirm", onClick: onConfirm, children: "Confirm Execution" }),
1223
1294
  onRevise && /* @__PURE__ */ jsx2(
1224
1295
  SecondaryActionButton,
1225
1296
  {
1226
1297
  type: "button",
1227
- "data-testid": "pde-ai-confirmation-revise",
1298
+ "data-testid": "confirmation-revise",
1228
1299
  onClick: onRevise,
1229
1300
  children: "Revise Plan"
1230
1301
  }
@@ -1321,10 +1392,10 @@ var SecondaryActionButton = styled(ActionButton)`
1321
1392
  border: 1px solid rgba(255, 255, 255, 0.14);
1322
1393
  `;
1323
1394
 
1324
- // src/components/chat-thread/components/pde-ai-notice-card.tsx
1395
+ // src/components/chat-thread/components/notice-card.tsx
1325
1396
  import styled2 from "@emotion/styled";
1326
1397
  import { jsx as jsx3 } from "@emotion/react/jsx-runtime";
1327
- var PDEAINoticeCard = ({ text, tone }) => /* @__PURE__ */ jsx3(Card2, { "data-testid": "pde-ai-notice-card", "data-tone": tone, children: text });
1398
+ var NoticeCard = ({ text, tone }) => /* @__PURE__ */ jsx3(Card2, { "data-testid": "notice-card", "data-tone": tone, children: text });
1328
1399
  var Card2 = styled2.div`
1329
1400
  padding: 12px 14px;
1330
1401
  border-radius: 16px;
@@ -1350,10 +1421,10 @@ var Card2 = styled2.div`
1350
1421
  }
1351
1422
  `;
1352
1423
 
1353
- // src/components/chat-thread/components/pde-ai-parameter-summary-card.tsx
1424
+ // src/components/chat-thread/components/parameter-summary-card.tsx
1354
1425
  import styled3 from "@emotion/styled";
1355
1426
  import { jsx as jsx4, jsxs as jsxs2 } from "@emotion/react/jsx-runtime";
1356
- var PDEAIParameterSummaryCard = ({ items }) => /* @__PURE__ */ jsxs2(Card3, { "data-testid": "pde-ai-parameter-summary-card", children: [
1427
+ var ParameterSummaryCard = ({ items }) => /* @__PURE__ */ jsxs2(Card3, { "data-testid": "parameter-summary-card", children: [
1357
1428
  /* @__PURE__ */ jsx4(Title2, { children: "Parameter Summary" }),
1358
1429
  /* @__PURE__ */ jsx4(List, { children: items.map((item) => /* @__PURE__ */ jsxs2(ListItem, { children: [
1359
1430
  /* @__PURE__ */ jsx4(Label, { children: item.label }),
@@ -1397,11 +1468,17 @@ var Value = styled3.span`
1397
1468
  text-align: right;
1398
1469
  `;
1399
1470
 
1400
- // src/components/chat-thread/components/pde-ai-questionnaire-card.tsx
1471
+ // src/components/chat-thread/components/questionnaire-card.tsx
1401
1472
  import { useState as useState2 } from "react";
1402
1473
  import styled4 from "@emotion/styled";
1403
1474
  import { jsx as jsx5, jsxs as jsxs3 } from "@emotion/react/jsx-runtime";
1404
1475
  var OTHER_OPTION_VALUE = "__other__";
1476
+ var DEFAULT_QUESTIONNAIRE_CARD_LABELS = {
1477
+ submitting: "Submitting...",
1478
+ submitted: "Selection submitted. Waiting for the plan to continue...",
1479
+ validationPrefix: "Please complete:",
1480
+ submitFailed: "Failed to submit. Please try again."
1481
+ };
1405
1482
  var createInitialAnswers = (questionnaire) => ({
1406
1483
  ...questionnaire.answers ?? {}
1407
1484
  });
@@ -1495,26 +1572,40 @@ var normalizeQuestionAnswer = (question, answer) => {
1495
1572
  return answer;
1496
1573
  }
1497
1574
  };
1498
- var PDEAIQuestionnaireCardInner = ({
1575
+ var QuestionnaireCardInner = ({
1499
1576
  questionnaire,
1500
1577
  interactive = false,
1501
- onSubmit
1578
+ onSubmit,
1579
+ labels
1502
1580
  }) => {
1503
1581
  const [answers, setAnswers] = useState2(
1504
1582
  () => createInitialAnswers(questionnaire)
1505
1583
  );
1506
1584
  const [errorMessage, setErrorMessage] = useState2(null);
1507
- const handleSubmit = () => {
1585
+ const [isSubmitting, setIsSubmitting] = useState2(false);
1586
+ const [isSubmitted, setIsSubmitted] = useState2(false);
1587
+ const resolvedLabels = {
1588
+ ...DEFAULT_QUESTIONNAIRE_CARD_LABELS,
1589
+ ...labels
1590
+ };
1591
+ const hasExternalFailureStatus = questionnaire.status === "expired" || questionnaire.status === "failed";
1592
+ const visibleErrorMessage = questionnaire.statusMessage ?? errorMessage;
1593
+ const isInteractionLocked = !interactive || isSubmitting || isSubmitted || hasExternalFailureStatus;
1594
+ const handleSubmit = async () => {
1595
+ if (isSubmitting || isSubmitted) {
1596
+ return;
1597
+ }
1508
1598
  const missingQuestions = questionnaire.questions.filter(
1509
1599
  (question) => question.required && isMissingRequiredAnswer(question, answers)
1510
1600
  );
1511
1601
  if (missingQuestions.length > 0) {
1512
1602
  setErrorMessage(
1513
- `Please complete: ${missingQuestions.map((question) => question.label).join(", ")}`
1603
+ `${resolvedLabels.validationPrefix} ${missingQuestions.map((question) => question.label).join(", ")}`
1514
1604
  );
1515
1605
  return;
1516
1606
  }
1517
1607
  setErrorMessage(null);
1608
+ setIsSubmitting(true);
1518
1609
  const normalizedAnswers = Object.fromEntries(
1519
1610
  questionnaire.questions.flatMap((question) => {
1520
1611
  const value = normalizeQuestionAnswer(question, answers[question.id]);
@@ -1531,11 +1622,18 @@ var PDEAIQuestionnaireCardInner = ({
1531
1622
  return [`- ${question.label}: ${formatQuestionAnswer(question, value)}`];
1532
1623
  })
1533
1624
  ];
1534
- onSubmit?.({
1535
- questionnaireId: questionnaire.questionnaireId,
1536
- answers: normalizedAnswers,
1537
- content: contentLines.join("\n")
1538
- });
1625
+ try {
1626
+ await onSubmit?.({
1627
+ questionnaireId: questionnaire.questionnaireId,
1628
+ answers: normalizedAnswers,
1629
+ content: contentLines.join("\n")
1630
+ });
1631
+ setIsSubmitted(true);
1632
+ } catch (error) {
1633
+ setErrorMessage(error instanceof Error ? error.message : resolvedLabels.submitFailed);
1634
+ } finally {
1635
+ setIsSubmitting(false);
1636
+ }
1539
1637
  };
1540
1638
  const renderQuestion = (question) => {
1541
1639
  const renderOptionChoice = ({
@@ -1550,13 +1648,13 @@ var PDEAIQuestionnaireCardInner = ({
1550
1648
  OptionChoiceItem,
1551
1649
  {
1552
1650
  role: "button",
1553
- tabIndex: interactive ? 0 : -1,
1651
+ tabIndex: isInteractionLocked ? -1 : 0,
1554
1652
  "aria-pressed": isSelected,
1555
1653
  "data-selected": isSelected,
1556
1654
  "data-tone": tone,
1557
- "data-testid": `pde-ai-question-option-${questionId}-${index}`,
1655
+ "data-testid": `question-option-${questionId}-${index}`,
1558
1656
  onClick: (event) => {
1559
- if (!interactive) {
1657
+ if (isInteractionLocked) {
1560
1658
  return;
1561
1659
  }
1562
1660
  if (event.target instanceof HTMLElement && event.target.closest("input")) {
@@ -1565,7 +1663,7 @@ var PDEAIQuestionnaireCardInner = ({
1565
1663
  onClick();
1566
1664
  },
1567
1665
  onKeyDown: (event) => {
1568
- if (!interactive) {
1666
+ if (isInteractionLocked) {
1569
1667
  return;
1570
1668
  }
1571
1669
  if (event.key !== "Enter" && event.key !== " ") {
@@ -1624,11 +1722,11 @@ var PDEAIQuestionnaireCardInner = ({
1624
1722
  inlineInput: singleSelectDraft.selectedValue === OTHER_OPTION_VALUE ? /* @__PURE__ */ jsx5(
1625
1723
  InlineOtherInput,
1626
1724
  {
1627
- "data-testid": `pde-ai-question-input-${question.id}`,
1725
+ "data-testid": `question-input-${question.id}`,
1628
1726
  type: "text",
1629
1727
  value: singleSelectDraft.otherValue,
1630
1728
  placeholder: "Other",
1631
- readOnly: !interactive,
1729
+ readOnly: isInteractionLocked,
1632
1730
  onClick: (event) => {
1633
1731
  event.stopPropagation();
1634
1732
  },
@@ -1649,11 +1747,11 @@ var PDEAIQuestionnaireCardInner = ({
1649
1747
  return /* @__PURE__ */ jsx5(QuestionBody, { children: /* @__PURE__ */ jsx5(
1650
1748
  TextInput,
1651
1749
  {
1652
- "data-testid": `pde-ai-question-input-${question.id}`,
1750
+ "data-testid": `question-input-${question.id}`,
1653
1751
  type: "text",
1654
1752
  value: getTextInputValue(answers[question.id]),
1655
1753
  placeholder: question.placeholder,
1656
- readOnly: !interactive,
1754
+ readOnly: isInteractionLocked,
1657
1755
  onChange: (event) => {
1658
1756
  setAnswers((current) => updateAnswerValue(current, question.id, event.target.value));
1659
1757
  }
@@ -1664,11 +1762,11 @@ var PDEAIQuestionnaireCardInner = ({
1664
1762
  /* @__PURE__ */ jsx5(
1665
1763
  TextInput,
1666
1764
  {
1667
- "data-testid": `pde-ai-question-input-${question.id}`,
1765
+ "data-testid": `question-input-${question.id}`,
1668
1766
  type: "number",
1669
1767
  value: getNumberInputValue(answers[question.id]),
1670
1768
  placeholder: question.placeholder,
1671
- readOnly: !interactive,
1769
+ readOnly: isInteractionLocked,
1672
1770
  onChange: (event) => {
1673
1771
  setAnswers(
1674
1772
  (current) => updateAnswerValue(
@@ -1703,7 +1801,7 @@ var PDEAIQuestionnaireCardInner = ({
1703
1801
  return null;
1704
1802
  }
1705
1803
  };
1706
- return /* @__PURE__ */ jsxs3(Card4, { "data-testid": "pde-ai-questionnaire-card", children: [
1804
+ return /* @__PURE__ */ jsxs3(Card4, { "data-testid": "questionnaire-card", children: [
1707
1805
  questionnaire.title ? /* @__PURE__ */ jsx5(Title3, { children: questionnaire.title }) : null,
1708
1806
  questionnaire.description ? /* @__PURE__ */ jsx5(Description, { children: questionnaire.description }) : null,
1709
1807
  /* @__PURE__ */ jsx5(QuestionList, { children: questionnaire.questions.map((question) => /* @__PURE__ */ jsxs3(QuestionCard, { children: [
@@ -1713,24 +1811,28 @@ var PDEAIQuestionnaireCardInner = ({
1713
1811
  ] }),
1714
1812
  renderQuestion(question)
1715
1813
  ] }, question.id)) }),
1716
- errorMessage ? /* @__PURE__ */ jsx5(ErrorMessage, { "data-testid": "pde-ai-questionnaire-error", children: errorMessage }) : null,
1717
- interactive ? /* @__PURE__ */ jsx5(
1814
+ visibleErrorMessage ? /* @__PURE__ */ jsx5(ErrorMessage, { "data-testid": "questionnaire-error", children: visibleErrorMessage }) : null,
1815
+ isSubmitted ? /* @__PURE__ */ jsx5(SuccessMessage, { "data-testid": "questionnaire-success", children: resolvedLabels.submitted }) : interactive && !hasExternalFailureStatus ? /* @__PURE__ */ jsx5(
1718
1816
  SubmitButton,
1719
1817
  {
1720
1818
  type: "button",
1721
- "data-testid": "pde-ai-questionnaire-submit",
1722
- onClick: handleSubmit,
1723
- children: questionnaire.submitLabel ?? "Submit"
1819
+ "data-testid": "questionnaire-submit",
1820
+ disabled: isInteractionLocked,
1821
+ onClick: () => {
1822
+ void handleSubmit();
1823
+ },
1824
+ children: isSubmitting ? resolvedLabels.submitting : questionnaire.submitLabel ?? "Submit"
1724
1825
  }
1725
1826
  ) : null
1726
1827
  ] });
1727
1828
  };
1728
1829
  var getQuestionnaireStateKey = (questionnaire) => JSON.stringify([
1729
1830
  questionnaire.questionnaireId,
1730
- questionnaire.answers ?? {},
1731
- questionnaire.questions
1831
+ questionnaire.questions,
1832
+ questionnaire.status,
1833
+ questionnaire.statusMessage
1732
1834
  ]);
1733
- var PDEAIQuestionnaireCard = (props) => /* @__PURE__ */ jsx5(PDEAIQuestionnaireCardInner, { ...props }, getQuestionnaireStateKey(props.questionnaire));
1835
+ var QuestionnaireCard = (props) => /* @__PURE__ */ jsx5(QuestionnaireCardInner, { ...props }, getQuestionnaireStateKey(props.questionnaire));
1734
1836
  var Card4 = styled4.section`
1735
1837
  display: grid;
1736
1838
  gap: 14px;
@@ -1878,6 +1980,10 @@ var ErrorMessage = styled4.div`
1878
1980
  color: rgba(255, 145, 145, 0.96);
1879
1981
  font-size: 12px;
1880
1982
  `;
1983
+ var SuccessMessage = styled4.div`
1984
+ color: rgba(164, 255, 210, 0.96);
1985
+ font-size: 12px;
1986
+ `;
1881
1987
  var SubmitButton = styled4.button`
1882
1988
  justify-self: flex-start;
1883
1989
  border: none;
@@ -1888,12 +1994,17 @@ var SubmitButton = styled4.button`
1888
1994
  font-weight: 700;
1889
1995
  padding: 10px 14px;
1890
1996
  cursor: pointer;
1997
+
1998
+ &:disabled {
1999
+ cursor: default;
2000
+ opacity: 0.72;
2001
+ }
1891
2002
  `;
1892
2003
 
1893
- // src/components/chat-thread/components/pde-ai-result-summary-card.tsx
2004
+ // src/components/chat-thread/components/result-summary-card.tsx
1894
2005
  import styled5 from "@emotion/styled";
1895
2006
  import { jsx as jsx6, jsxs as jsxs4 } from "@emotion/react/jsx-runtime";
1896
- var PDEAIResultSummaryCard = ({ summary }) => /* @__PURE__ */ jsxs4(Card5, { "data-testid": "pde-ai-result-summary-card", "data-status": summary.status, children: [
2007
+ var ResultSummaryCard = ({ summary }) => /* @__PURE__ */ jsxs4(Card5, { "data-testid": "result-summary-card", "data-status": summary.status, children: [
1897
2008
  /* @__PURE__ */ jsx6(Eyebrow2, { children: summary.status }),
1898
2009
  /* @__PURE__ */ jsx6(Headline, { children: summary.headline }),
1899
2010
  /* @__PURE__ */ jsx6(Details, { children: summary.details.map((detail) => /* @__PURE__ */ jsx6(Detail, { children: detail }, detail)) })
@@ -1989,6 +2100,7 @@ var ImageViewer = ({ src, alt, onClose }) => {
1989
2100
  import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs5 } from "@emotion/react/jsx-runtime";
1990
2101
  var MARKDOWN_REMARK_PLUGINS = [remarkGfm, remarkMath];
1991
2102
  var MARKDOWN_REHYPE_PLUGINS = [rehypeKatex];
2103
+ var USER_MESSAGE_COLLAPSE_HEIGHT_PX = 120;
1992
2104
  var MARKDOWN_COMPONENTS = {
1993
2105
  table: ({ node: _node, ...props }) => /* @__PURE__ */ jsx8(TableWrapper, { children: /* @__PURE__ */ jsx8("table", { ...props }) })
1994
2106
  };
@@ -2001,10 +2113,101 @@ var renderMarkdownContent = (content) => /* @__PURE__ */ jsx8(
2001
2113
  children: content
2002
2114
  }
2003
2115
  );
2116
+ var renderPlainTextContent = (content) => content;
2117
+ var CollapseIcon = ({ expanded }) => /* @__PURE__ */ jsx8(
2118
+ "svg",
2119
+ {
2120
+ "aria-hidden": "true",
2121
+ width: "16",
2122
+ height: "16",
2123
+ viewBox: "0 0 16 16",
2124
+ fill: "none",
2125
+ xmlns: "http://www.w3.org/2000/svg",
2126
+ children: /* @__PURE__ */ jsx8(
2127
+ "path",
2128
+ {
2129
+ d: "M4 6l4 4 4-4",
2130
+ stroke: "currentColor",
2131
+ strokeWidth: "1.6",
2132
+ strokeLinecap: "round",
2133
+ strokeLinejoin: "round",
2134
+ transform: expanded ? "rotate(180 8 8)" : void 0
2135
+ }
2136
+ )
2137
+ }
2138
+ );
2139
+ var useUserMessageCollapse = ({
2140
+ blocks,
2141
+ displayedBlocks,
2142
+ displayedContent,
2143
+ enabled,
2144
+ freshContent,
2145
+ settledContent
2146
+ }) => {
2147
+ const [isCollapsible, setIsCollapsible] = useState3(false);
2148
+ const [isExpanded, setIsExpanded] = useState3(false);
2149
+ const [bodyStackElement, setBodyStackElement] = useState3(null);
2150
+ const syncCollapseState = useCallback2(
2151
+ (element) => {
2152
+ const nextCollapsible = enabled && (element?.scrollHeight ?? 0) > USER_MESSAGE_COLLAPSE_HEIGHT_PX;
2153
+ setIsCollapsible(nextCollapsible);
2154
+ if (!nextCollapsible) {
2155
+ setIsExpanded(false);
2156
+ }
2157
+ },
2158
+ [enabled]
2159
+ );
2160
+ const bodyStackRef = useCallback2(
2161
+ (node) => {
2162
+ setBodyStackElement(node);
2163
+ syncCollapseState(node);
2164
+ },
2165
+ [syncCollapseState]
2166
+ );
2167
+ useLayoutEffect(() => {
2168
+ if (!bodyStackElement) {
2169
+ return;
2170
+ }
2171
+ const frameId = requestAnimationFrame(() => {
2172
+ syncCollapseState(bodyStackElement);
2173
+ });
2174
+ return () => {
2175
+ cancelAnimationFrame(frameId);
2176
+ };
2177
+ }, [
2178
+ blocks,
2179
+ bodyStackElement,
2180
+ displayedBlocks,
2181
+ displayedContent,
2182
+ enabled,
2183
+ freshContent,
2184
+ settledContent,
2185
+ syncCollapseState
2186
+ ]);
2187
+ useLayoutEffect(() => {
2188
+ if (!bodyStackElement || !enabled || typeof ResizeObserver === "undefined") {
2189
+ return;
2190
+ }
2191
+ const observer = new ResizeObserver(() => {
2192
+ syncCollapseState(bodyStackElement);
2193
+ });
2194
+ observer.observe(bodyStackElement);
2195
+ return () => {
2196
+ observer.disconnect();
2197
+ };
2198
+ }, [bodyStackElement, enabled, syncCollapseState]);
2199
+ return {
2200
+ bodyStackRef,
2201
+ isCollapsed: isCollapsible && !isExpanded,
2202
+ isCollapsible,
2203
+ isExpanded,
2204
+ toggleExpanded: () => setIsExpanded((current) => !current)
2205
+ };
2206
+ };
2004
2207
  var createExecutionConfirmationContent = (proposal) => [
2005
2208
  "Execution confirmed",
2006
- `- Equation: ${proposal.equationName}`,
2007
- ...proposal.solverName ? [`- Solver: ${proposal.solverName}`] : [],
2209
+ `- Equation: ${proposal.resourceName}`,
2210
+ ...proposal.executorName ? [`- Solver: ${proposal.executorName}`] : [],
2008
2211
  `- Proposal ID: ${proposal.proposalId}`
2009
2212
  ].join("\n");
2010
2213
  var areChatAttachmentsEqual = (previousAttachments, nextAttachments) => {
@@ -2026,10 +2229,10 @@ var areParameterSummaryItemsEqual = (previousItems, nextItems) => previousItems.
2026
2229
  const nextItem = nextItems[index];
2027
2230
  return item.label === nextItem?.label && item.value === nextItem.value && item.fieldPath === nextItem.fieldPath;
2028
2231
  });
2029
- 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(
2232
+ 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(
2030
2233
  (warning, index) => warning === nextProposal.warnings?.[index]
2031
2234
  );
2032
- 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]);
2235
+ 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]);
2033
2236
  var areStringArraysEqual = (previousValues, nextValues) => previousValues.length === nextValues.length && previousValues.every((value, index) => value === nextValues[index]);
2034
2237
  var areQuestionAnswersEqual = (previousAnswer, nextAnswer) => {
2035
2238
  if (Array.isArray(previousAnswer) || Array.isArray(nextAnswer)) {
@@ -2070,7 +2273,7 @@ var arePlanQuestionsEqual = (previousQuestion, nextQuestion) => {
2070
2273
  return false;
2071
2274
  }
2072
2275
  };
2073
- 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(
2276
+ 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(
2074
2277
  (question, index) => arePlanQuestionsEqual(question, nextQuestionnaire.questions[index])
2075
2278
  ) && areQuestionAnswerMapsEqual(previousQuestionnaire.answers, nextQuestionnaire.answers);
2076
2279
  var areMessageBlocksEqual = (previousBlocks, nextBlocks) => {
@@ -2151,6 +2354,8 @@ var ChatMessageItemView = ({
2151
2354
  const canSubmitConfirmation = isPlanMode && typeof onConfirmationSubmit === "function";
2152
2355
  const canSubmitQuestionnaire = isPlanMode && typeof onQuestionnaireSubmit === "function";
2153
2356
  const shouldShowStreamingCaret = isAssistantStreaming && (!shouldRenderStructuredBlocks || hasTextContent);
2357
+ const isUserMessage = message.role === "user";
2358
+ const messageRenderMode = isUserMessage ? "plain-text" : "markdown";
2154
2359
  const timelineConsumedText = messageRenderOrder === "timeline" ? getTimelineConsumedText(blocks) : "";
2155
2360
  const hasConsumedTimelineText = timelineConsumedText.length > 0 && displayedContent.startsWith(timelineConsumedText);
2156
2361
  const timelineDisplayedContent = hasConsumedTimelineText ? displayedContent.slice(timelineConsumedText.length) : displayedContent;
@@ -2167,6 +2372,21 @@ var ChatMessageItemView = ({
2167
2372
  message,
2168
2373
  messageRenderOrder
2169
2374
  });
2375
+ const {
2376
+ bodyStackRef,
2377
+ isCollapsed: isUserMessageCollapsed,
2378
+ isCollapsible: isUserMessageCollapsible,
2379
+ isExpanded: isUserMessageExpanded,
2380
+ toggleExpanded: toggleUserMessageExpanded
2381
+ } = useUserMessageCollapse({
2382
+ blocks,
2383
+ displayedBlocks,
2384
+ displayedContent,
2385
+ enabled: isUserMessage,
2386
+ freshContent,
2387
+ settledContent
2388
+ });
2389
+ const renderMessageContent = (content) => messageRenderMode === "plain-text" ? renderPlainTextContent(content) : renderMarkdownContent(content);
2170
2390
  const renderChatMessageBlock = (block, index) => {
2171
2391
  switch (block.type) {
2172
2392
  case "markdown":
@@ -2175,17 +2395,18 @@ var ChatMessageItemView = ({
2175
2395
  {
2176
2396
  "data-testid": `chat-message-block-${index}`,
2177
2397
  "data-block-tone": "settled",
2178
- children: renderMarkdownContent(block.text)
2398
+ "data-render-mode": messageRenderMode,
2399
+ children: renderMessageContent(block.text)
2179
2400
  },
2180
2401
  `markdown-${index}`
2181
2402
  );
2182
2403
  case "notice":
2183
- return /* @__PURE__ */ jsx8(Fragment, { children: /* @__PURE__ */ jsx8(PDEAINoticeCard, { text: block.text, tone: block.tone }) }, `notice-${index}`);
2404
+ return /* @__PURE__ */ jsx8(Fragment, { children: /* @__PURE__ */ jsx8(NoticeCard, { text: block.text, tone: block.tone }) }, `notice-${index}`);
2184
2405
  case "parameter_summary":
2185
- return /* @__PURE__ */ jsx8(Fragment, { children: /* @__PURE__ */ jsx8(PDEAIParameterSummaryCard, { items: block.items }) }, `parameter-summary-${index}`);
2406
+ return /* @__PURE__ */ jsx8(Fragment, { children: /* @__PURE__ */ jsx8(ParameterSummaryCard, { items: block.items }) }, `parameter-summary-${index}`);
2186
2407
  case "confirmation_card":
2187
2408
  return /* @__PURE__ */ jsx8(Fragment, { children: /* @__PURE__ */ jsx8(
2188
- PDEAIExecutionConfirmationCard,
2409
+ ExecutionConfirmationCard,
2189
2410
  {
2190
2411
  proposal: block.proposal,
2191
2412
  interactive: isPlanMode,
@@ -2197,13 +2418,19 @@ var ChatMessageItemView = ({
2197
2418
  }
2198
2419
  ) }, `confirmation-card-${index}`);
2199
2420
  case "result_summary":
2200
- return /* @__PURE__ */ jsx8(Fragment, { children: /* @__PURE__ */ jsx8(PDEAIResultSummaryCard, { summary: block.summary }) }, `result-summary-${index}`);
2421
+ return /* @__PURE__ */ jsx8(Fragment, { children: /* @__PURE__ */ jsx8(ResultSummaryCard, { summary: block.summary }) }, `result-summary-${index}`);
2201
2422
  case "questionnaire":
2202
2423
  return /* @__PURE__ */ jsx8(Fragment, { children: /* @__PURE__ */ jsx8(
2203
- PDEAIQuestionnaireCard,
2424
+ QuestionnaireCard,
2204
2425
  {
2205
2426
  questionnaire: block.questionnaire,
2206
2427
  interactive: canSubmitQuestionnaire,
2428
+ labels: {
2429
+ submitting: labels.questionnaireSubmitting,
2430
+ submitted: labels.questionnaireSubmitted,
2431
+ validationPrefix: labels.questionnaireValidationPrefix,
2432
+ submitFailed: labels.questionnaireSubmitFailed
2433
+ },
2207
2434
  onSubmit: canSubmitQuestionnaire ? (submission) => onQuestionnaireSubmit({
2208
2435
  ...submission,
2209
2436
  sourceMessageId: message.id
@@ -2236,14 +2463,31 @@ var ChatMessageItemView = ({
2236
2463
  "data-testid": block.tone === "fresh" ? "chat-message-fresh-block" : "chat-message-settled-block",
2237
2464
  "data-block-tone": block.tone,
2238
2465
  "data-block-index": index,
2239
- children: renderMarkdownContent(block.content)
2466
+ "data-render-mode": messageRenderMode,
2467
+ children: renderMessageContent(block.content)
2240
2468
  },
2241
2469
  `${block.tone}-${index}`
2242
2470
  )),
2243
- !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
2471
+ !textBlocks.some((block) => block.content) && !settledText && !freshText && Boolean(textContent) ? /* @__PURE__ */ jsx8(
2472
+ ContentBlock,
2473
+ {
2474
+ "data-testid": "chat-message-settled-block",
2475
+ "data-block-tone": "settled",
2476
+ "data-render-mode": messageRenderMode,
2477
+ children: renderMessageContent(textContent)
2478
+ }
2479
+ ) : null
2244
2480
  ] });
2245
2481
  };
2246
- const renderStaticTextSegment = (content) => /* @__PURE__ */ jsx8(ContentBlock, { "data-testid": "chat-message-settled-block", "data-block-tone": "settled", children: renderMarkdownContent(content) });
2482
+ const renderStaticTextSegment = (content) => /* @__PURE__ */ jsx8(
2483
+ ContentBlock,
2484
+ {
2485
+ "data-testid": "chat-message-settled-block",
2486
+ "data-block-tone": "settled",
2487
+ "data-render-mode": messageRenderMode,
2488
+ children: renderMessageContent(content)
2489
+ }
2490
+ );
2247
2491
  const bodySegments = (() => {
2248
2492
  if (!shouldRenderStructuredBlocks && hasTextContent) {
2249
2493
  return [{ type: "text" }];
@@ -2301,21 +2545,40 @@ var ChatMessageItemView = ({
2301
2545
  }
2302
2546
  ) : null,
2303
2547
  /* @__PURE__ */ jsx8(Role, { children: message.role === "user" ? labels.userRoleLabel : labels.assistantRoleLabel }),
2548
+ isUserMessageCollapsible ? /* @__PURE__ */ jsx8(
2549
+ CollapseToggle,
2550
+ {
2551
+ type: "button",
2552
+ "data-testid": "chat-message-collapse-toggle",
2553
+ "aria-label": isUserMessageExpanded ? labels.collapseMessageAriaLabel : labels.expandMessageAriaLabel,
2554
+ "aria-expanded": isUserMessageExpanded,
2555
+ onClick: toggleUserMessageExpanded,
2556
+ children: /* @__PURE__ */ jsx8(CollapseIcon, { expanded: isUserMessageExpanded })
2557
+ }
2558
+ ) : null,
2304
2559
  isStoppedAssistant ? /* @__PURE__ */ jsx8(StatusTag, { "data-testid": "chat-message-stopped-tag", children: labels.stoppedResponse }) : null
2305
2560
  ] }),
2306
2561
  /* @__PURE__ */ jsxs5(Content, { "data-testid": "chat-message-content", children: [
2307
- shouldRenderStructuredBlocks || hasTextContent ? /* @__PURE__ */ jsx8(ContentStack, { "data-testid": "chat-message-body-stack", children: bodySegments.map((segment, index) => /* @__PURE__ */ jsx8(
2308
- ContentSegment,
2562
+ shouldRenderStructuredBlocks || hasTextContent ? /* @__PURE__ */ jsx8(
2563
+ ContentStack,
2309
2564
  {
2310
- "data-testid": "chat-message-content-segment",
2311
- children: segment.type === "block" ? renderChatMessageBlock(segment.block, segment.index) : segment.type === "text" ? segment.content !== void 0 ? segment.useTimelineSegmentation ? renderTextContent({
2312
- content: segment.content,
2313
- displayedBlocks: segment.displayedBlocks,
2314
- useTimelineSegmentation: true
2315
- }) : renderStaticTextSegment(segment.content) : renderTextContent() : renderStaticTextSegment(segment.content)
2316
- },
2317
- segment.type === "text" ? `text-${index}` : segment.type === "markdown" ? `markdown-${index}` : `${segment.block.type}-${segment.index}`
2318
- )) }) : null,
2565
+ ref: bodyStackRef,
2566
+ "data-testid": "chat-message-body-stack",
2567
+ "data-collapsed": isUserMessageCollapsed,
2568
+ children: bodySegments.map((segment, index) => /* @__PURE__ */ jsx8(
2569
+ ContentSegment,
2570
+ {
2571
+ "data-testid": "chat-message-content-segment",
2572
+ children: segment.type === "block" ? renderChatMessageBlock(segment.block, segment.index) : segment.type === "text" ? segment.content !== void 0 ? segment.useTimelineSegmentation ? renderTextContent({
2573
+ content: segment.content,
2574
+ displayedBlocks: segment.displayedBlocks,
2575
+ useTimelineSegmentation: true
2576
+ }) : renderStaticTextSegment(segment.content) : renderTextContent() : renderStaticTextSegment(segment.content)
2577
+ },
2578
+ segment.type === "text" ? `text-${index}` : segment.type === "markdown" ? `markdown-${index}` : `${segment.block.type}-${segment.index}`
2579
+ ))
2580
+ }
2581
+ ) : null,
2319
2582
  attachments.length ? /* @__PURE__ */ jsx8(AttachmentGrid, { "data-testid": "chat-message-attachment-grid", children: attachments.map((attachment) => /* @__PURE__ */ jsx8(
2320
2583
  AttachmentButton,
2321
2584
  {
@@ -2400,6 +2663,29 @@ var StatusTag = styled7.span`
2400
2663
  letter-spacing: 0.02em;
2401
2664
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
2402
2665
  `;
2666
+ var CollapseToggle = styled7.button`
2667
+ margin-left: auto;
2668
+ width: 28px;
2669
+ height: 28px;
2670
+ display: inline-flex;
2671
+ align-items: center;
2672
+ justify-content: center;
2673
+ border: 1px solid rgba(255, 255, 255, 0.12);
2674
+ border-radius: 999px;
2675
+ background: rgba(255, 255, 255, 0.06);
2676
+ color: rgba(255, 255, 255, 0.72);
2677
+ cursor: pointer;
2678
+ transition:
2679
+ background 160ms ease-out,
2680
+ border-color 160ms ease-out,
2681
+ color 160ms ease-out;
2682
+
2683
+ &:hover {
2684
+ background: rgba(255, 255, 255, 0.1);
2685
+ border-color: rgba(255, 255, 255, 0.18);
2686
+ color: rgba(255, 255, 255, 0.92);
2687
+ }
2688
+ `;
2403
2689
  var Content = styled7.div`
2404
2690
  color: rgba(255, 255, 255, 0.92);
2405
2691
  line-height: 1.6;
@@ -2444,6 +2730,11 @@ var ContentStack = styled7.div`
2444
2730
  display: flex;
2445
2731
  flex-direction: column;
2446
2732
  gap: 16px;
2733
+
2734
+ &[data-collapsed='true'] {
2735
+ max-height: ${USER_MESSAGE_COLLAPSE_HEIGHT_PX}px;
2736
+ overflow: hidden;
2737
+ }
2447
2738
  `;
2448
2739
  var ContentSegment = styled7.div`
2449
2740
  min-width: 0;
@@ -2457,6 +2748,11 @@ var ContentBlock = styled7.div`
2457
2748
  margin-top: 16px;
2458
2749
  }
2459
2750
 
2751
+ &[data-render-mode='plain-text'] {
2752
+ white-space: pre-wrap;
2753
+ overflow-wrap: anywhere;
2754
+ }
2755
+
2460
2756
  &[data-block-tone='fresh'] {
2461
2757
  opacity: 0.85;
2462
2758
  filter: brightness(0.82) saturate(0.88);
@@ -2718,7 +3014,7 @@ var ChatThreadView = ({
2718
3014
  const latestUserMessageRef = useRef4(null);
2719
3015
  const reservedSpaceFrameRef = useRef4(null);
2720
3016
  const [latestTurnMinHeight, setLatestTurnMinHeight] = useState4(0);
2721
- const measureLatestTurnMinHeight = useCallback2(() => {
3017
+ const measureLatestTurnMinHeight = useCallback3(() => {
2722
3018
  const container = containerRef.current;
2723
3019
  if (!container)
2724
3020
  return;
@@ -2728,7 +3024,7 @@ var ChatThreadView = ({
2728
3024
  const nextMinHeight = Math.max(0, container.clientHeight - paddingTop - paddingBottom);
2729
3025
  setLatestTurnMinHeight((current) => current === nextMinHeight ? current : nextMinHeight);
2730
3026
  }, []);
2731
- const scrollLatestUserMessageToTop = useCallback2(() => {
3027
+ const scrollLatestUserMessageToTop = useCallback3(() => {
2732
3028
  const container = containerRef.current;
2733
3029
  const target = latestUserMessageRef.current;
2734
3030
  if (!container || !target)
@@ -2748,7 +3044,7 @@ var ChatThreadView = ({
2748
3044
  }
2749
3045
  container.scrollTop = nextScrollTop;
2750
3046
  }, []);
2751
- useLayoutEffect(() => {
3047
+ useLayoutEffect2(() => {
2752
3048
  if (reservedSpaceFrameRef.current !== null) {
2753
3049
  window.cancelAnimationFrame(reservedSpaceFrameRef.current);
2754
3050
  reservedSpaceFrameRef.current = null;
@@ -2777,7 +3073,7 @@ var ChatThreadView = ({
2777
3073
  }
2778
3074
  };
2779
3075
  }, [latestUserMessageId, measureLatestTurnMinHeight, scrollLatestUserMessageToTop]);
2780
- useLayoutEffect(() => {
3076
+ useLayoutEffect2(() => {
2781
3077
  if (!latestUserMessageId)
2782
3078
  return;
2783
3079
  const handleResize = () => {
@@ -2864,15 +3160,40 @@ var ChatThread = () => {
2864
3160
  const error = useChatStore((s) => s.errorBySession[s.activeSessionId ?? ""]);
2865
3161
  const updateQA = useChatStore((s) => s.updateQuestionnaireAnswers);
2866
3162
  const clearSessionError = useChatStore((s) => s.clearSessionError);
2867
- const { sendRef, retryRef, renderMessageBlock, labels } = useChatContext();
2868
- const handleRetry = useCallback2(() => {
3163
+ const {
3164
+ sendRef,
3165
+ retryRef,
3166
+ renderMessageBlock,
3167
+ handleQuestionnaireSubmit: customQuestionnaireSubmit,
3168
+ handleConfirmationSubmit: customConfirmationSubmit,
3169
+ labels
3170
+ } = useChatContext();
3171
+ const handleRetry = useCallback3(() => {
2869
3172
  if (!activeSessionId)
2870
3173
  return;
2871
3174
  clearSessionError(activeSessionId);
2872
3175
  void retryRef.current();
2873
3176
  }, [activeSessionId, clearSessionError, retryRef]);
2874
- const handleQuestionnaireSubmit = useCallback2(
2875
- (submission) => {
3177
+ const handleQuestionnaireSubmit = useCallback3(
3178
+ async (submission) => {
3179
+ if (customQuestionnaireSubmit) {
3180
+ const handled = await customQuestionnaireSubmit(submission, {
3181
+ sessionId: activeSessionId ?? void 0,
3182
+ mode: activeSessionMode
3183
+ });
3184
+ if (handled !== false) {
3185
+ if (activeSessionId && submission.sourceMessageId) {
3186
+ updateQA(
3187
+ activeSessionId,
3188
+ submission.sourceMessageId,
3189
+ submission.questionnaireId,
3190
+ submission.answers
3191
+ );
3192
+ }
3193
+ return;
3194
+ }
3195
+ }
3196
+ await sendRef.current(submission.content);
2876
3197
  if (activeSessionId && submission.sourceMessageId) {
2877
3198
  updateQA(
2878
3199
  activeSessionId,
@@ -2881,15 +3202,23 @@ var ChatThread = () => {
2881
3202
  submission.answers
2882
3203
  );
2883
3204
  }
2884
- void sendRef.current(submission.content);
2885
3205
  },
2886
- [activeSessionId, updateQA, sendRef]
3206
+ [activeSessionId, activeSessionMode, updateQA, sendRef, customQuestionnaireSubmit]
2887
3207
  );
2888
- const handleConfirmationSubmit = useCallback2(
2889
- (submission) => {
2890
- void sendRef.current(submission.content);
3208
+ const handleConfirmation = useCallback3(
3209
+ async (submission) => {
3210
+ if (customConfirmationSubmit) {
3211
+ const handled = await customConfirmationSubmit(submission, {
3212
+ sessionId: activeSessionId ?? void 0,
3213
+ mode: activeSessionMode
3214
+ });
3215
+ if (handled !== false) {
3216
+ return;
3217
+ }
3218
+ }
3219
+ await sendRef.current(submission.content);
2891
3220
  },
2892
- [sendRef]
3221
+ [activeSessionId, activeSessionMode, sendRef, customConfirmationSubmit]
2893
3222
  );
2894
3223
  if (!hasSessions || messages.length === 0 && !streamingMessage) {
2895
3224
  return /* @__PURE__ */ jsx10(ChatThreadEmptyState, {});
@@ -2903,7 +3232,7 @@ var ChatThread = () => {
2903
3232
  error,
2904
3233
  retryButtonLabel: labels.retryButton,
2905
3234
  onRetry: handleRetry,
2906
- onConfirmationSubmit: handleConfirmationSubmit,
3235
+ onConfirmationSubmit: handleConfirmation,
2907
3236
  onQuestionnaireSubmit: handleQuestionnaireSubmit,
2908
3237
  renderMessageBlock
2909
3238
  }
@@ -2917,6 +3246,7 @@ var Container = styled9.div`
2917
3246
  min-height: 0;
2918
3247
  overflow: auto;
2919
3248
  padding: 24px;
3249
+ margin-bottom: 24px;
2920
3250
  overscroll-behavior: contain;
2921
3251
 
2922
3252
  &::-webkit-scrollbar {
@@ -2970,7 +3300,7 @@ var RetryButton = styled9.button`
2970
3300
  `;
2971
3301
 
2972
3302
  // src/components/chat-composer/index.tsx
2973
- import { useEffect as useEffect6, useRef as useRef7 } from "react";
3303
+ import { useEffect as useEffect6, useLayoutEffect as useLayoutEffect3, useRef as useRef7, useState as useState8 } from "react";
2974
3304
  import styled14 from "@emotion/styled";
2975
3305
 
2976
3306
  // src/components/chat-composer/lib/chat-composer.ts
@@ -3082,7 +3412,7 @@ var resolveSendSession = ({
3082
3412
  };
3083
3413
 
3084
3414
  // src/components/chat-composer/hooks/use-chat-composer.ts
3085
- import { useCallback as useCallback3, useEffect as useEffect5, useRef as useRef6, useState as useState6 } from "react";
3415
+ import { useCallback as useCallback4, useEffect as useEffect5, useRef as useRef6, useState as useState6 } from "react";
3086
3416
 
3087
3417
  // src/components/chat-composer/hooks/use-composer-attachments.ts
3088
3418
  import { useEffect as useEffect4, useRef as useRef5, useState as useState5 } from "react";
@@ -3220,7 +3550,7 @@ var useChatComposer = () => {
3220
3550
  const [availableModels, setAvailableModels] = useState6([]);
3221
3551
  const [isModelsLoading, setIsModelsLoading] = useState6(true);
3222
3552
  const [isModelsError, setIsModelsError] = useState6(false);
3223
- const fetchModels = useCallback3(async () => {
3553
+ const fetchModels = useCallback4(async () => {
3224
3554
  setIsModelsLoading(true);
3225
3555
  setIsModelsError(false);
3226
3556
  try {
@@ -3275,7 +3605,7 @@ var useChatComposer = () => {
3275
3605
  stopRequestRef.current.timeoutId = null;
3276
3606
  }
3277
3607
  };
3278
- const clearStopRequest = useCallback3((sessionId) => {
3608
+ const clearStopRequest = useCallback4((sessionId) => {
3279
3609
  if (!stopRequestRef.current)
3280
3610
  return;
3281
3611
  if (sessionId && stopRequestRef.current.sessionId !== sessionId)
@@ -3283,7 +3613,7 @@ var useChatComposer = () => {
3283
3613
  clearStopTimeout(sessionId);
3284
3614
  stopRequestRef.current = null;
3285
3615
  }, []);
3286
- const finalizeStop = useCallback3(
3616
+ const finalizeStop = useCallback4(
3287
3617
  (sessionId) => {
3288
3618
  if (stopRequestRef.current?.sessionId === sessionId) {
3289
3619
  if (stopRequestRef.current.finalized)
@@ -3298,7 +3628,7 @@ var useChatComposer = () => {
3298
3628
  },
3299
3629
  [clearStopRequest, finalizeStoppedStreamingMessage]
3300
3630
  );
3301
- const runStream = useCallback3(
3631
+ const runStream = useCallback4(
3302
3632
  async ({
3303
3633
  localSessionId,
3304
3634
  sessionId,
@@ -3387,7 +3717,7 @@ var useChatComposer = () => {
3387
3717
  setSessionError
3388
3718
  ]
3389
3719
  );
3390
- const send = useCallback3(
3720
+ const send = useCallback4(
3391
3721
  async (contentOverride) => {
3392
3722
  const content = (contentOverride ?? value).trim();
3393
3723
  const hasText = Boolean(content);
@@ -3996,6 +4326,27 @@ var StopSpinner = styled13.span`
3996
4326
 
3997
4327
  // src/components/chat-composer/index.tsx
3998
4328
  import { jsx as jsx15, jsxs as jsxs10 } from "@emotion/react/jsx-runtime";
4329
+ var CHAT_COMPOSER_LINE_HEIGHT_PX = 20;
4330
+ var CHAT_COMPOSER_MAX_ROWS = 7;
4331
+ var CHAT_COMPOSER_PADDING_TOP_PX = 8;
4332
+ var CHAT_COMPOSER_PADDING_BOTTOM_PX = 12;
4333
+ var CHAT_COMPOSER_PADDING_BLOCK_PX = CHAT_COMPOSER_PADDING_TOP_PX + CHAT_COMPOSER_PADDING_BOTTOM_PX;
4334
+ var CHAT_COMPOSER_MIN_ROWS = 4;
4335
+ var CHAT_COMPOSER_EXPANDED_MAX_ROWS = 60;
4336
+ var CHAT_COMPOSER_EXPANDED_MAX_VIEWPORT_RATIO = 0.7;
4337
+ var CHAT_COMPOSER_EXPANDED_RESERVED_SPACE_PX = 96;
4338
+ var CHAT_COMPOSER_MAX_HEIGHT_PX = CHAT_COMPOSER_MAX_ROWS * CHAT_COMPOSER_LINE_HEIGHT_PX + CHAT_COMPOSER_PADDING_BLOCK_PX;
4339
+ var CHAT_COMPOSER_EXPANDED_ROWS_HEIGHT_PX = CHAT_COMPOSER_EXPANDED_MAX_ROWS * CHAT_COMPOSER_LINE_HEIGHT_PX + CHAT_COMPOSER_PADDING_BLOCK_PX;
4340
+ var getExpandedComposerHeightPx = () => {
4341
+ if (typeof window === "undefined") {
4342
+ return CHAT_COMPOSER_EXPANDED_ROWS_HEIGHT_PX;
4343
+ }
4344
+ const viewportLimitedHeight = window.innerHeight * CHAT_COMPOSER_EXPANDED_MAX_VIEWPORT_RATIO - CHAT_COMPOSER_EXPANDED_RESERVED_SPACE_PX;
4345
+ return Math.max(
4346
+ CHAT_COMPOSER_MAX_HEIGHT_PX,
4347
+ Math.floor(Math.min(CHAT_COMPOSER_EXPANDED_ROWS_HEIGHT_PX, viewportLimitedHeight))
4348
+ );
4349
+ };
3999
4350
  var PlusIcon = () => /* @__PURE__ */ jsx15(
4000
4351
  "svg",
4001
4352
  {
@@ -4008,6 +4359,36 @@ var PlusIcon = () => /* @__PURE__ */ jsx15(
4008
4359
  children: /* @__PURE__ */ jsx15("path", { d: "M8 3v10M3 8h10", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round" })
4009
4360
  }
4010
4361
  );
4362
+ var ComposerExpandIcon = ({ expanded }) => /* @__PURE__ */ jsx15(
4363
+ "svg",
4364
+ {
4365
+ "aria-hidden": "true",
4366
+ width: "16",
4367
+ height: "16",
4368
+ viewBox: "0 0 16 16",
4369
+ fill: "none",
4370
+ xmlns: "http://www.w3.org/2000/svg",
4371
+ children: expanded ? /* @__PURE__ */ jsx15(
4372
+ "path",
4373
+ {
4374
+ d: "M14 6h-4V2M10 6l4-4M2 10h4v4M6 10l-4 4",
4375
+ stroke: "currentColor",
4376
+ strokeWidth: "1.5",
4377
+ strokeLinecap: "round",
4378
+ strokeLinejoin: "round"
4379
+ }
4380
+ ) : /* @__PURE__ */ jsx15(
4381
+ "path",
4382
+ {
4383
+ d: "M10 2h4v4M14 2L9 7M6 14H2v-4M2 14l5-5",
4384
+ stroke: "currentColor",
4385
+ strokeWidth: "1.5",
4386
+ strokeLinecap: "round",
4387
+ strokeLinejoin: "round"
4388
+ }
4389
+ )
4390
+ }
4391
+ );
4011
4392
  var ChatComposerView = ({
4012
4393
  value,
4013
4394
  placeholder,
@@ -4024,6 +4405,8 @@ var ChatComposerView = ({
4024
4405
  isStopping,
4025
4406
  enableImageAttachments,
4026
4407
  modeLabels,
4408
+ expandComposerAriaLabel,
4409
+ collapseComposerAriaLabel,
4027
4410
  onValueChange,
4028
4411
  onPickImages,
4029
4412
  onPasteImages,
@@ -4035,6 +4418,9 @@ var ChatComposerView = ({
4035
4418
  onSend
4036
4419
  }) => {
4037
4420
  const imageInputRef = useRef7(null);
4421
+ const inputRef = useRef7(null);
4422
+ const [isComposerExpandable, setIsComposerExpandable] = useState8(false);
4423
+ const [isComposerExpanded, setIsComposerExpanded] = useState8(false);
4038
4424
  const canSend = canSendChatMessage({
4039
4425
  value,
4040
4426
  attachmentCount: attachments.length,
@@ -4042,6 +4428,26 @@ var ChatComposerView = ({
4042
4428
  isModelsError,
4043
4429
  hasModels
4044
4430
  });
4431
+ useLayoutEffect3(() => {
4432
+ const element = inputRef.current;
4433
+ if (!element) {
4434
+ return;
4435
+ }
4436
+ if (!isComposerExpanded) {
4437
+ element.style.height = "0px";
4438
+ }
4439
+ const scrollHeight = element.scrollHeight;
4440
+ const nextExpandable = scrollHeight > CHAT_COMPOSER_MAX_HEIGHT_PX;
4441
+ const expandedHeight = getExpandedComposerHeightPx();
4442
+ const nextHeight = isComposerExpanded ? expandedHeight : Math.min(scrollHeight, CHAT_COMPOSER_MAX_HEIGHT_PX);
4443
+ setIsComposerExpandable(nextExpandable);
4444
+ element.style.height = `${nextHeight}px`;
4445
+ element.style.overflowY = !isComposerExpanded && scrollHeight > CHAT_COMPOSER_MAX_HEIGHT_PX ? "auto" : "hidden";
4446
+ }, [isComposerExpanded, value]);
4447
+ const handleSend = async () => {
4448
+ setIsComposerExpanded(false);
4449
+ await onSend();
4450
+ };
4045
4451
  const handleKeyDown = (event) => {
4046
4452
  if (shouldStopChatComposer({ key: event.key, shiftKey: event.shiftKey, isStreaming })) {
4047
4453
  event.preventDefault();
@@ -4051,7 +4457,7 @@ var ChatComposerView = ({
4051
4457
  if (!shouldSubmitChatComposer({ key: event.key, shiftKey: event.shiftKey, canSend }))
4052
4458
  return;
4053
4459
  event.preventDefault();
4054
- void onSend();
4460
+ void handleSend();
4055
4461
  };
4056
4462
  const handlePickImages = (event) => {
4057
4463
  if (event.target.files?.length)
@@ -4086,60 +4492,77 @@ var ChatComposerView = ({
4086
4492
  }
4087
4493
  ),
4088
4494
  attachmentNotice === "limit_reached" ? /* @__PURE__ */ jsx15(AttachmentNotice, { "data-testid": "chat-composer-attachment-notice", children: attachmentLimitNotice }) : null,
4089
- /* @__PURE__ */ jsx15(
4090
- Input,
4091
- {
4092
- "data-testid": "chat-composer-input",
4093
- value,
4094
- onChange: (event) => onValueChange(event.target.value),
4095
- onKeyDown: handleKeyDown,
4096
- onPaste: enableImageAttachments ? handlePaste : void 0,
4097
- placeholder
4098
- }
4099
- ),
4100
- /* @__PURE__ */ jsx15(Footer, { children: /* @__PURE__ */ jsxs10(Actions2, { "data-testid": "chat-composer-actions", children: [
4101
- enableImageAttachments ? /* @__PURE__ */ jsx15(
4102
- AttachButton,
4495
+ /* @__PURE__ */ jsxs10(InputArea, { "data-testid": "chat-composer-input-area", children: [
4496
+ isComposerExpanded || isComposerExpandable ? /* @__PURE__ */ jsx15(
4497
+ ComposerExpandButton,
4103
4498
  {
4104
4499
  type: "button",
4105
- "data-testid": "chat-composer-attach-image",
4106
- "aria-label": "Attach image",
4107
- onClick: () => imageInputRef.current?.click(),
4108
- children: /* @__PURE__ */ jsx15(PlusIcon, {})
4500
+ "data-testid": "chat-composer-expand-toggle",
4501
+ "aria-label": isComposerExpanded ? collapseComposerAriaLabel : expandComposerAriaLabel,
4502
+ "aria-expanded": isComposerExpanded,
4503
+ onClick: () => setIsComposerExpanded((current) => !current),
4504
+ children: /* @__PURE__ */ jsx15(ComposerExpandIcon, { expanded: isComposerExpanded })
4109
4505
  }
4110
4506
  ) : null,
4111
4507
  /* @__PURE__ */ jsx15(
4112
- ChatModeControl,
4113
- {
4114
- value: selectedMode,
4115
- disabled: isStreaming,
4116
- labels: modeLabels,
4117
- onChange: onSelectedModeChange
4118
- }
4119
- ),
4120
- /* @__PURE__ */ jsx15(
4121
- ChatModelControl,
4508
+ Input,
4122
4509
  {
4123
- selectedModel,
4124
- availableModels,
4125
- isModelsLoading,
4126
- isModelsError,
4127
- hasModels,
4128
- onSelectedModelChange,
4129
- onReloadModels
4510
+ ref: inputRef,
4511
+ "data-testid": "chat-composer-input",
4512
+ "data-expanded": isComposerExpanded,
4513
+ value,
4514
+ onChange: (event) => onValueChange(event.target.value),
4515
+ onKeyDown: handleKeyDown,
4516
+ onPaste: enableImageAttachments ? handlePaste : void 0,
4517
+ placeholder
4130
4518
  }
4131
- ),
4132
- /* @__PURE__ */ jsx15(
4133
- ChatSendActions,
4519
+ )
4520
+ ] }),
4521
+ /* @__PURE__ */ jsxs10(Footer, { children: [
4522
+ /* @__PURE__ */ jsx15(LeadingActions, { "data-testid": "chat-composer-leading-actions", children: enableImageAttachments ? /* @__PURE__ */ jsx15(
4523
+ AttachButton,
4134
4524
  {
4135
- canSend,
4136
- isStreaming,
4137
- isStopping,
4138
- onStop,
4139
- onSend
4525
+ type: "button",
4526
+ "data-testid": "chat-composer-attach-image",
4527
+ "aria-label": "Attach image",
4528
+ onClick: () => imageInputRef.current?.click(),
4529
+ children: /* @__PURE__ */ jsx15(PlusIcon, {})
4140
4530
  }
4141
- )
4142
- ] }) })
4531
+ ) : null }),
4532
+ /* @__PURE__ */ jsxs10(TrailingActions, { "data-testid": "chat-composer-trailing-actions", children: [
4533
+ /* @__PURE__ */ jsx15(
4534
+ ChatModeControl,
4535
+ {
4536
+ value: selectedMode,
4537
+ disabled: isStreaming,
4538
+ labels: modeLabels,
4539
+ onChange: onSelectedModeChange
4540
+ }
4541
+ ),
4542
+ /* @__PURE__ */ jsx15(
4543
+ ChatModelControl,
4544
+ {
4545
+ selectedModel,
4546
+ availableModels,
4547
+ isModelsLoading,
4548
+ isModelsError,
4549
+ hasModels,
4550
+ onSelectedModelChange,
4551
+ onReloadModels
4552
+ }
4553
+ ),
4554
+ /* @__PURE__ */ jsx15(
4555
+ ChatSendActions,
4556
+ {
4557
+ canSend,
4558
+ isStreaming,
4559
+ isStopping,
4560
+ onStop,
4561
+ onSend: handleSend
4562
+ }
4563
+ )
4564
+ ] })
4565
+ ] })
4143
4566
  ] }) });
4144
4567
  };
4145
4568
  var ChatComposer = () => {
@@ -4175,6 +4598,8 @@ var ChatComposer = () => {
4175
4598
  isStopping: state.isStopping,
4176
4599
  enableImageAttachments,
4177
4600
  modeLabels,
4601
+ expandComposerAriaLabel: labels.expandComposerAriaLabel,
4602
+ collapseComposerAriaLabel: labels.collapseComposerAriaLabel,
4178
4603
  onValueChange: actions.setValue,
4179
4604
  onPickImages: actions.pickImages,
4180
4605
  onPasteImages: actions.pasteImages,
@@ -4191,6 +4616,16 @@ var Container2 = styled14.div`
4191
4616
  padding: 0 16px 16px;
4192
4617
  `;
4193
4618
  var Surface = styled14.div`
4619
+ display: grid;
4620
+ grid-template-columns: minmax(0, 1fr);
4621
+ grid-template-areas:
4622
+ 'attachments'
4623
+ 'notice'
4624
+ 'input'
4625
+ 'footer';
4626
+ width: 100%;
4627
+ max-width: 760px;
4628
+ margin: 0 auto;
4194
4629
  background: var(--border-color);
4195
4630
  border-radius: 20px;
4196
4631
  border: 1px solid var(--border-hover);
@@ -4200,6 +4635,7 @@ var Surface = styled14.div`
4200
4635
  backdrop-filter: blur(10px);
4201
4636
  `;
4202
4637
  var AttachmentNotice = styled14.div`
4638
+ grid-area: notice;
4203
4639
  margin: 10px 12px 0;
4204
4640
  padding: 8px 10px;
4205
4641
  border-radius: 10px;
@@ -4209,19 +4645,45 @@ var AttachmentNotice = styled14.div`
4209
4645
  font-size: 12px;
4210
4646
  line-height: 1.4;
4211
4647
  `;
4648
+ var InputArea = styled14.div`
4649
+ grid-area: input;
4650
+ position: relative;
4651
+ `;
4212
4652
  var Input = styled14.textarea`
4653
+ --textarea-line-height: ${CHAT_COMPOSER_LINE_HEIGHT_PX}px;
4654
+ --textarea-min-rows: ${CHAT_COMPOSER_MIN_ROWS};
4655
+ --textarea-max-rows: ${CHAT_COMPOSER_MAX_ROWS};
4656
+ --textarea-expanded-max-rows: ${CHAT_COMPOSER_EXPANDED_MAX_ROWS};
4657
+ --textarea-padding-top: ${CHAT_COMPOSER_PADDING_TOP_PX}px;
4658
+ --textarea-padding-bottom: ${CHAT_COMPOSER_PADDING_BOTTOM_PX}px;
4659
+ --textarea-padding-block: calc(var(--textarea-padding-top) + var(--textarea-padding-bottom));
4660
+ --textarea-max-height: calc(
4661
+ var(--textarea-max-rows) * var(--textarea-line-height) + var(--textarea-padding-block)
4662
+ );
4663
+ --textarea-expanded-max-height: min(
4664
+ calc(
4665
+ var(--textarea-expanded-max-rows) * var(--textarea-line-height) +
4666
+ var(--textarea-padding-block)
4667
+ ),
4668
+ calc(70vh - ${CHAT_COMPOSER_EXPANDED_RESERVED_SPACE_PX}px)
4669
+ );
4213
4670
  width: 100%;
4214
- min-height: 96px;
4671
+ min-height: calc(
4672
+ var(--textarea-min-rows) * var(--textarea-line-height) + var(--textarea-padding-block)
4673
+ );
4674
+ max-height: var(--textarea-max-height);
4675
+ box-sizing: border-box;
4215
4676
  resize: none;
4216
4677
  appearance: none;
4217
4678
  border: 0;
4218
4679
  outline: 0;
4219
4680
  background: transparent;
4220
- padding: 8px 12px 12px 12px;
4681
+ padding: var(--textarea-padding-top) 44px var(--textarea-padding-bottom) 12px;
4221
4682
  font-weight: 400;
4222
4683
  font-size: 14px;
4223
4684
  color: var(--text-primary);
4224
- line-height: 20px;
4685
+ line-height: var(--textarea-line-height);
4686
+ overflow-y: hidden;
4225
4687
 
4226
4688
  &::placeholder {
4227
4689
  color: var(--text-secondary);
@@ -4230,19 +4692,50 @@ var Input = styled14.textarea`
4230
4692
  &::-webkit-resizer {
4231
4693
  display: none;
4232
4694
  }
4695
+
4696
+ &[data-expanded='true'] {
4697
+ max-height: var(--textarea-expanded-max-height);
4698
+ }
4699
+ `;
4700
+ var ComposerExpandButton = styled14.button`
4701
+ position: absolute;
4702
+ top: 8px;
4703
+ right: 10px;
4704
+ width: 28px;
4705
+ height: 28px;
4706
+ display: grid;
4707
+ place-items: center;
4708
+ border: none;
4709
+ border-radius: 999px;
4710
+ background: transparent;
4711
+ color: rgba(255, 255, 255, 0.72);
4712
+ cursor: pointer;
4713
+ z-index: 1;
4714
+
4715
+ &:hover {
4716
+ background: rgba(255, 255, 255, 0.08);
4717
+ color: rgba(255, 255, 255, 0.92);
4718
+ }
4233
4719
  `;
4234
4720
  var Footer = styled14.div`
4235
- display: flex;
4721
+ grid-area: footer;
4722
+ display: grid;
4723
+ grid-template-columns: minmax(0, 1fr) auto;
4236
4724
  align-items: flex-end;
4237
- justify-content: stretch;
4238
4725
  gap: 16px;
4239
4726
  padding: 0 14px 14px;
4240
4727
  `;
4241
- var Actions2 = styled14.div`
4728
+ var LeadingActions = styled14.div`
4729
+ display: flex;
4730
+ align-items: center;
4731
+ justify-content: flex-start;
4732
+ gap: 8px;
4733
+ min-width: 0;
4734
+ `;
4735
+ var TrailingActions = styled14.div`
4242
4736
  display: flex;
4243
4737
  align-items: center;
4244
4738
  flex-wrap: wrap;
4245
- width: 100%;
4246
4739
  min-width: 0;
4247
4740
  justify-content: flex-end;
4248
4741
  gap: 8px;