@xinghunm/ai-chat 0.4.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -31,10 +31,18 @@ var DEFAULT_AI_CHAT_LABELS = {
31
31
  attachmentLimitNotice: "Images exceeded the limit. Only the first 10 images were kept.",
32
32
  userRoleLabel: "User",
33
33
  assistantRoleLabel: "Assistant",
34
+ expandMessageAriaLabel: "Expand message",
35
+ collapseMessageAriaLabel: "Collapse message",
36
+ expandComposerAriaLabel: "Expand composer",
37
+ collapseComposerAriaLabel: "Collapse composer",
34
38
  stoppedResponse: "Response stopped",
35
39
  assistantStreamingAriaLabel: "assistant streaming",
36
40
  networkError: "Network request failed. Please try again.",
37
- genericError: "Something went wrong. Please try again."
41
+ genericError: "Something went wrong. Please try again.",
42
+ questionnaireSubmitting: "Submitting...",
43
+ questionnaireSubmitted: "Selection submitted. Waiting for the plan to continue...",
44
+ questionnaireValidationPrefix: "Please complete:",
45
+ questionnaireSubmitFailed: "Failed to submit. Please try again."
38
46
  };
39
47
 
40
48
  // src/store/chat-store.ts
@@ -50,6 +58,24 @@ var resolveSessionTitleFromMessage = (message) => {
50
58
  }
51
59
  return DEFAULT_CHAT_SESSION_TITLE;
52
60
  };
61
+ var mergeStreamingBlocks = (existingBlocks, incomingBlocks) => {
62
+ const nextBlocks = [...existingBlocks ?? []];
63
+ incomingBlocks.forEach((incomingBlock) => {
64
+ if (incomingBlock.type !== "questionnaire") {
65
+ nextBlocks.push(incomingBlock);
66
+ return;
67
+ }
68
+ const existingIndex = nextBlocks.findIndex(
69
+ (block) => block.type === "questionnaire" && block.questionnaire.questionnaireId === incomingBlock.questionnaire.questionnaireId
70
+ );
71
+ if (existingIndex === -1) {
72
+ nextBlocks.push(incomingBlock);
73
+ return;
74
+ }
75
+ nextBlocks[existingIndex] = incomingBlock;
76
+ });
77
+ return nextBlocks;
78
+ };
53
79
  var finalizeStreamingMessage = (state, sessionId, status, clearError = false) => {
54
80
  const message = state.streamingMessageBySession[sessionId];
55
81
  const hasRenderableMessagePayload = Boolean(
@@ -239,10 +265,15 @@ var createChatStore = (initialState) => createStore((set, get) => ({
239
265
  const target = state.streamingMessageBySession[sessionId];
240
266
  if (!target)
241
267
  return;
268
+ const nextBlocks = patch.blocks !== void 0 ? mergeStreamingBlocks(target.blocks, patch.blocks) : target.blocks;
242
269
  set({
243
270
  streamingMessageBySession: {
244
271
  ...state.streamingMessageBySession,
245
- [sessionId]: { ...target, ...patch }
272
+ [sessionId]: {
273
+ ...target,
274
+ ...patch,
275
+ ...patch.blocks !== void 0 ? { blocks: nextBlocks } : {}
276
+ }
246
277
  }
247
278
  });
248
279
  },
@@ -462,9 +493,27 @@ var DEFAULT_CHAT_TRANSPORT_ENDPOINTS = {
462
493
  completions: "/chat/completions",
463
494
  terminate: "/chat/terminate"
464
495
  };
496
+ var createToolExecutionHeaders = (policy) => {
497
+ if (!policy?.enabled) {
498
+ return {};
499
+ }
500
+ return {
501
+ "X-Tool-Approval-Required": String(Boolean(policy.approvalRequired)),
502
+ ...typeof policy.approvalTimeoutSec === "number" ? { "X-Tool-Approval-Timeout": String(policy.approvalTimeoutSec) } : {}
503
+ };
504
+ };
505
+ var createModeDefaultHeaders = (mode) => {
506
+ if (mode === "ask" || mode === "plan") {
507
+ return {
508
+ "X-Tool-Approval-Required": "false"
509
+ };
510
+ }
511
+ return {};
512
+ };
465
513
  var createDefaultChatTransport = ({
466
514
  apiBaseUrl,
467
515
  authToken,
516
+ toolExecutionPolicy,
468
517
  streamHeaders,
469
518
  transformStreamPacket,
470
519
  endpoints,
@@ -475,6 +524,10 @@ var createDefaultChatTransport = ({
475
524
  ...DEFAULT_CHAT_TRANSPORT_ENDPOINTS,
476
525
  ...endpoints
477
526
  };
527
+ const resolvedStreamHeaders = {
528
+ ...createToolExecutionHeaders(toolExecutionPolicy),
529
+ ...streamHeaders
530
+ };
478
531
  return {
479
532
  getModels: () => getChatModels(client, resolvedEndpoints.models),
480
533
  startStream: async ({
@@ -488,12 +541,16 @@ var createDefaultChatTransport = ({
488
541
  onDone,
489
542
  onError
490
543
  }) => {
544
+ const requestHeaders = {
545
+ ...createModeDefaultHeaders(mode),
546
+ ...resolvedStreamHeaders
547
+ };
491
548
  await startChatStream({
492
549
  apiBaseUrl,
493
550
  endpointPath: resolvedEndpoints.completions,
494
551
  sessionId,
495
552
  authToken,
496
- requestHeaders: streamHeaders,
553
+ requestHeaders,
497
554
  model,
498
555
  mode,
499
556
  content,
@@ -522,6 +579,8 @@ var AiChatProvider = (props) => {
522
579
  defaultMode,
523
580
  labels,
524
581
  renderMessageBlock,
582
+ handleQuestionnaireSubmit,
583
+ handleConfirmationSubmit,
525
584
  messageRenderOrder,
526
585
  enableImageAttachments = true,
527
586
  children
@@ -577,6 +636,8 @@ var AiChatProvider = (props) => {
577
636
  sendRef,
578
637
  retryRef,
579
638
  renderMessageBlock,
639
+ handleQuestionnaireSubmit,
640
+ handleConfirmationSubmit,
580
641
  messageRenderOrder,
581
642
  transformStreamPacket: defaultTransformStreamPacket,
582
643
  enableImageAttachments
@@ -587,6 +648,8 @@ var AiChatProvider = (props) => {
587
648
  defaultAuthToken,
588
649
  defaultTransformStreamPacket,
589
650
  enableImageAttachments,
651
+ handleConfirmationSubmit,
652
+ handleQuestionnaireSubmit,
590
653
  labels,
591
654
  messageRenderOrder,
592
655
  renderMessageBlock,
@@ -600,7 +663,7 @@ var AiChatProvider = (props) => {
600
663
  };
601
664
 
602
665
  // src/components/chat-thread/index.tsx
603
- import { useCallback as useCallback2, useLayoutEffect, useMemo as useMemo4, useRef as useRef4, useState as useState4 } from "react";
666
+ import { useCallback as useCallback3, useLayoutEffect as useLayoutEffect2, useMemo as useMemo4, useRef as useRef4, useState as useState4 } from "react";
604
667
  import styled9 from "@emotion/styled";
605
668
 
606
669
  // src/context/use-chat-context.ts
@@ -621,7 +684,7 @@ var useChatStore = (selector) => {
621
684
  var CHAT_THREAD_SCROLL_TOP_GAP = 16;
622
685
 
623
686
  // src/components/chat-thread/components/chat-message-item.tsx
624
- import { Fragment, memo, useState as useState3 } from "react";
687
+ import { Fragment, memo, useCallback as useCallback2, useLayoutEffect, useState as useState3 } from "react";
625
688
  import styled7 from "@emotion/styled";
626
689
  import { keyframes } from "@emotion/react";
627
690
  import ReactMarkdown from "react-markdown";
@@ -896,7 +959,7 @@ var getTimelineBlockKey = (block, index) => {
896
959
  case "confirmation_card":
897
960
  return `${index}:confirmation_card:${block.proposal.proposalId}`;
898
961
  case "result_summary":
899
- return `${index}:result_summary:${block.summary.taskId}:${block.summary.status}`;
962
+ return `${index}:result_summary:${block.summary.summaryId}:${block.summary.status}`;
900
963
  case "questionnaire":
901
964
  return `${index}:questionnaire:${block.questionnaire.questionnaireId}`;
902
965
  case "custom":
@@ -1188,21 +1251,21 @@ var useTimelineBlockAnchors = ({
1188
1251
  };
1189
1252
  };
1190
1253
 
1191
- // src/components/chat-thread/components/pde-ai-execution-confirmation-card.tsx
1254
+ // src/components/chat-thread/components/execution-confirmation-card.tsx
1192
1255
  import styled from "@emotion/styled";
1193
1256
  import { jsx as jsx2, jsxs } from "@emotion/react/jsx-runtime";
1194
- var PDEAIExecutionConfirmationCard = ({
1257
+ var ExecutionConfirmationCard = ({
1195
1258
  proposal,
1196
1259
  interactive = false,
1197
1260
  onApply,
1198
1261
  onConfirm,
1199
1262
  onRevise
1200
1263
  }) => {
1201
- return /* @__PURE__ */ jsxs(Card, { "data-testid": "pde-ai-execution-confirmation-card", children: [
1264
+ return /* @__PURE__ */ jsxs(Card, { "data-testid": "execution-confirmation-card", children: [
1202
1265
  /* @__PURE__ */ jsxs(Header, { children: [
1203
1266
  /* @__PURE__ */ jsx2(Eyebrow, { children: "Execution Proposal" }),
1204
- /* @__PURE__ */ jsx2(Title, { children: proposal.equationName }),
1205
- proposal.solverName ? /* @__PURE__ */ jsx2(Subtitle, { children: proposal.solverName }) : null
1267
+ /* @__PURE__ */ jsx2(Title, { children: proposal.resourceName }),
1268
+ proposal.executorName ? /* @__PURE__ */ jsx2(Subtitle, { children: proposal.executorName }) : null
1206
1269
  ] }),
1207
1270
  /* @__PURE__ */ jsx2(SummaryList, { children: proposal.parameterSummary.map((item) => /* @__PURE__ */ jsxs(SummaryItem, { children: [
1208
1271
  /* @__PURE__ */ jsx2(SummaryLabel, { children: item.label }),
@@ -1210,21 +1273,13 @@ var PDEAIExecutionConfirmationCard = ({
1210
1273
  ] }, `${item.fieldPath ?? item.label}-${item.value}`)) }),
1211
1274
  proposal.warnings?.length ? /* @__PURE__ */ jsx2(Warnings, { children: proposal.warnings.map((warning) => /* @__PURE__ */ jsx2(Warning, { children: warning }, warning)) }) : null,
1212
1275
  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
- ),
1276
+ onApply && /* @__PURE__ */ jsx2(ActionButton, { type: "button", "data-testid": "confirmation-apply", onClick: onApply, children: "Apply to Parameters" }),
1277
+ onConfirm && /* @__PURE__ */ jsx2(ActionButton, { type: "button", "data-testid": "confirmation-confirm", onClick: onConfirm, children: "Confirm Execution" }),
1223
1278
  onRevise && /* @__PURE__ */ jsx2(
1224
1279
  SecondaryActionButton,
1225
1280
  {
1226
1281
  type: "button",
1227
- "data-testid": "pde-ai-confirmation-revise",
1282
+ "data-testid": "confirmation-revise",
1228
1283
  onClick: onRevise,
1229
1284
  children: "Revise Plan"
1230
1285
  }
@@ -1321,10 +1376,10 @@ var SecondaryActionButton = styled(ActionButton)`
1321
1376
  border: 1px solid rgba(255, 255, 255, 0.14);
1322
1377
  `;
1323
1378
 
1324
- // src/components/chat-thread/components/pde-ai-notice-card.tsx
1379
+ // src/components/chat-thread/components/notice-card.tsx
1325
1380
  import styled2 from "@emotion/styled";
1326
1381
  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 });
1382
+ var NoticeCard = ({ text, tone }) => /* @__PURE__ */ jsx3(Card2, { "data-testid": "notice-card", "data-tone": tone, children: text });
1328
1383
  var Card2 = styled2.div`
1329
1384
  padding: 12px 14px;
1330
1385
  border-radius: 16px;
@@ -1350,10 +1405,10 @@ var Card2 = styled2.div`
1350
1405
  }
1351
1406
  `;
1352
1407
 
1353
- // src/components/chat-thread/components/pde-ai-parameter-summary-card.tsx
1408
+ // src/components/chat-thread/components/parameter-summary-card.tsx
1354
1409
  import styled3 from "@emotion/styled";
1355
1410
  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: [
1411
+ var ParameterSummaryCard = ({ items }) => /* @__PURE__ */ jsxs2(Card3, { "data-testid": "parameter-summary-card", children: [
1357
1412
  /* @__PURE__ */ jsx4(Title2, { children: "Parameter Summary" }),
1358
1413
  /* @__PURE__ */ jsx4(List, { children: items.map((item) => /* @__PURE__ */ jsxs2(ListItem, { children: [
1359
1414
  /* @__PURE__ */ jsx4(Label, { children: item.label }),
@@ -1397,11 +1452,17 @@ var Value = styled3.span`
1397
1452
  text-align: right;
1398
1453
  `;
1399
1454
 
1400
- // src/components/chat-thread/components/pde-ai-questionnaire-card.tsx
1455
+ // src/components/chat-thread/components/questionnaire-card.tsx
1401
1456
  import { useState as useState2 } from "react";
1402
1457
  import styled4 from "@emotion/styled";
1403
1458
  import { jsx as jsx5, jsxs as jsxs3 } from "@emotion/react/jsx-runtime";
1404
1459
  var OTHER_OPTION_VALUE = "__other__";
1460
+ var DEFAULT_QUESTIONNAIRE_CARD_LABELS = {
1461
+ submitting: "Submitting...",
1462
+ submitted: "Selection submitted. Waiting for the plan to continue...",
1463
+ validationPrefix: "Please complete:",
1464
+ submitFailed: "Failed to submit. Please try again."
1465
+ };
1405
1466
  var createInitialAnswers = (questionnaire) => ({
1406
1467
  ...questionnaire.answers ?? {}
1407
1468
  });
@@ -1495,26 +1556,40 @@ var normalizeQuestionAnswer = (question, answer) => {
1495
1556
  return answer;
1496
1557
  }
1497
1558
  };
1498
- var PDEAIQuestionnaireCardInner = ({
1559
+ var QuestionnaireCardInner = ({
1499
1560
  questionnaire,
1500
1561
  interactive = false,
1501
- onSubmit
1562
+ onSubmit,
1563
+ labels
1502
1564
  }) => {
1503
1565
  const [answers, setAnswers] = useState2(
1504
1566
  () => createInitialAnswers(questionnaire)
1505
1567
  );
1506
1568
  const [errorMessage, setErrorMessage] = useState2(null);
1507
- const handleSubmit = () => {
1569
+ const [isSubmitting, setIsSubmitting] = useState2(false);
1570
+ const [isSubmitted, setIsSubmitted] = useState2(false);
1571
+ const resolvedLabels = {
1572
+ ...DEFAULT_QUESTIONNAIRE_CARD_LABELS,
1573
+ ...labels
1574
+ };
1575
+ const hasExternalFailureStatus = questionnaire.status === "expired" || questionnaire.status === "failed";
1576
+ const visibleErrorMessage = questionnaire.statusMessage ?? errorMessage;
1577
+ const isInteractionLocked = !interactive || isSubmitting || isSubmitted || hasExternalFailureStatus;
1578
+ const handleSubmit = async () => {
1579
+ if (isSubmitting || isSubmitted) {
1580
+ return;
1581
+ }
1508
1582
  const missingQuestions = questionnaire.questions.filter(
1509
1583
  (question) => question.required && isMissingRequiredAnswer(question, answers)
1510
1584
  );
1511
1585
  if (missingQuestions.length > 0) {
1512
1586
  setErrorMessage(
1513
- `Please complete: ${missingQuestions.map((question) => question.label).join(", ")}`
1587
+ `${resolvedLabels.validationPrefix} ${missingQuestions.map((question) => question.label).join(", ")}`
1514
1588
  );
1515
1589
  return;
1516
1590
  }
1517
1591
  setErrorMessage(null);
1592
+ setIsSubmitting(true);
1518
1593
  const normalizedAnswers = Object.fromEntries(
1519
1594
  questionnaire.questions.flatMap((question) => {
1520
1595
  const value = normalizeQuestionAnswer(question, answers[question.id]);
@@ -1531,11 +1606,18 @@ var PDEAIQuestionnaireCardInner = ({
1531
1606
  return [`- ${question.label}: ${formatQuestionAnswer(question, value)}`];
1532
1607
  })
1533
1608
  ];
1534
- onSubmit?.({
1535
- questionnaireId: questionnaire.questionnaireId,
1536
- answers: normalizedAnswers,
1537
- content: contentLines.join("\n")
1538
- });
1609
+ try {
1610
+ await onSubmit?.({
1611
+ questionnaireId: questionnaire.questionnaireId,
1612
+ answers: normalizedAnswers,
1613
+ content: contentLines.join("\n")
1614
+ });
1615
+ setIsSubmitted(true);
1616
+ } catch (error) {
1617
+ setErrorMessage(error instanceof Error ? error.message : resolvedLabels.submitFailed);
1618
+ } finally {
1619
+ setIsSubmitting(false);
1620
+ }
1539
1621
  };
1540
1622
  const renderQuestion = (question) => {
1541
1623
  const renderOptionChoice = ({
@@ -1550,13 +1632,13 @@ var PDEAIQuestionnaireCardInner = ({
1550
1632
  OptionChoiceItem,
1551
1633
  {
1552
1634
  role: "button",
1553
- tabIndex: interactive ? 0 : -1,
1635
+ tabIndex: isInteractionLocked ? -1 : 0,
1554
1636
  "aria-pressed": isSelected,
1555
1637
  "data-selected": isSelected,
1556
1638
  "data-tone": tone,
1557
- "data-testid": `pde-ai-question-option-${questionId}-${index}`,
1639
+ "data-testid": `question-option-${questionId}-${index}`,
1558
1640
  onClick: (event) => {
1559
- if (!interactive) {
1641
+ if (isInteractionLocked) {
1560
1642
  return;
1561
1643
  }
1562
1644
  if (event.target instanceof HTMLElement && event.target.closest("input")) {
@@ -1565,7 +1647,7 @@ var PDEAIQuestionnaireCardInner = ({
1565
1647
  onClick();
1566
1648
  },
1567
1649
  onKeyDown: (event) => {
1568
- if (!interactive) {
1650
+ if (isInteractionLocked) {
1569
1651
  return;
1570
1652
  }
1571
1653
  if (event.key !== "Enter" && event.key !== " ") {
@@ -1624,11 +1706,11 @@ var PDEAIQuestionnaireCardInner = ({
1624
1706
  inlineInput: singleSelectDraft.selectedValue === OTHER_OPTION_VALUE ? /* @__PURE__ */ jsx5(
1625
1707
  InlineOtherInput,
1626
1708
  {
1627
- "data-testid": `pde-ai-question-input-${question.id}`,
1709
+ "data-testid": `question-input-${question.id}`,
1628
1710
  type: "text",
1629
1711
  value: singleSelectDraft.otherValue,
1630
1712
  placeholder: "Other",
1631
- readOnly: !interactive,
1713
+ readOnly: isInteractionLocked,
1632
1714
  onClick: (event) => {
1633
1715
  event.stopPropagation();
1634
1716
  },
@@ -1649,11 +1731,11 @@ var PDEAIQuestionnaireCardInner = ({
1649
1731
  return /* @__PURE__ */ jsx5(QuestionBody, { children: /* @__PURE__ */ jsx5(
1650
1732
  TextInput,
1651
1733
  {
1652
- "data-testid": `pde-ai-question-input-${question.id}`,
1734
+ "data-testid": `question-input-${question.id}`,
1653
1735
  type: "text",
1654
1736
  value: getTextInputValue(answers[question.id]),
1655
1737
  placeholder: question.placeholder,
1656
- readOnly: !interactive,
1738
+ readOnly: isInteractionLocked,
1657
1739
  onChange: (event) => {
1658
1740
  setAnswers((current) => updateAnswerValue(current, question.id, event.target.value));
1659
1741
  }
@@ -1664,11 +1746,11 @@ var PDEAIQuestionnaireCardInner = ({
1664
1746
  /* @__PURE__ */ jsx5(
1665
1747
  TextInput,
1666
1748
  {
1667
- "data-testid": `pde-ai-question-input-${question.id}`,
1749
+ "data-testid": `question-input-${question.id}`,
1668
1750
  type: "number",
1669
1751
  value: getNumberInputValue(answers[question.id]),
1670
1752
  placeholder: question.placeholder,
1671
- readOnly: !interactive,
1753
+ readOnly: isInteractionLocked,
1672
1754
  onChange: (event) => {
1673
1755
  setAnswers(
1674
1756
  (current) => updateAnswerValue(
@@ -1703,7 +1785,7 @@ var PDEAIQuestionnaireCardInner = ({
1703
1785
  return null;
1704
1786
  }
1705
1787
  };
1706
- return /* @__PURE__ */ jsxs3(Card4, { "data-testid": "pde-ai-questionnaire-card", children: [
1788
+ return /* @__PURE__ */ jsxs3(Card4, { "data-testid": "questionnaire-card", children: [
1707
1789
  questionnaire.title ? /* @__PURE__ */ jsx5(Title3, { children: questionnaire.title }) : null,
1708
1790
  questionnaire.description ? /* @__PURE__ */ jsx5(Description, { children: questionnaire.description }) : null,
1709
1791
  /* @__PURE__ */ jsx5(QuestionList, { children: questionnaire.questions.map((question) => /* @__PURE__ */ jsxs3(QuestionCard, { children: [
@@ -1713,24 +1795,28 @@ var PDEAIQuestionnaireCardInner = ({
1713
1795
  ] }),
1714
1796
  renderQuestion(question)
1715
1797
  ] }, question.id)) }),
1716
- errorMessage ? /* @__PURE__ */ jsx5(ErrorMessage, { "data-testid": "pde-ai-questionnaire-error", children: errorMessage }) : null,
1717
- interactive ? /* @__PURE__ */ jsx5(
1798
+ visibleErrorMessage ? /* @__PURE__ */ jsx5(ErrorMessage, { "data-testid": "questionnaire-error", children: visibleErrorMessage }) : null,
1799
+ isSubmitted ? /* @__PURE__ */ jsx5(SuccessMessage, { "data-testid": "questionnaire-success", children: resolvedLabels.submitted }) : interactive && !hasExternalFailureStatus ? /* @__PURE__ */ jsx5(
1718
1800
  SubmitButton,
1719
1801
  {
1720
1802
  type: "button",
1721
- "data-testid": "pde-ai-questionnaire-submit",
1722
- onClick: handleSubmit,
1723
- children: questionnaire.submitLabel ?? "Submit"
1803
+ "data-testid": "questionnaire-submit",
1804
+ disabled: isInteractionLocked,
1805
+ onClick: () => {
1806
+ void handleSubmit();
1807
+ },
1808
+ children: isSubmitting ? resolvedLabels.submitting : questionnaire.submitLabel ?? "Submit"
1724
1809
  }
1725
1810
  ) : null
1726
1811
  ] });
1727
1812
  };
1728
1813
  var getQuestionnaireStateKey = (questionnaire) => JSON.stringify([
1729
1814
  questionnaire.questionnaireId,
1730
- questionnaire.answers ?? {},
1731
- questionnaire.questions
1815
+ questionnaire.questions,
1816
+ questionnaire.status,
1817
+ questionnaire.statusMessage
1732
1818
  ]);
1733
- var PDEAIQuestionnaireCard = (props) => /* @__PURE__ */ jsx5(PDEAIQuestionnaireCardInner, { ...props }, getQuestionnaireStateKey(props.questionnaire));
1819
+ var QuestionnaireCard = (props) => /* @__PURE__ */ jsx5(QuestionnaireCardInner, { ...props }, getQuestionnaireStateKey(props.questionnaire));
1734
1820
  var Card4 = styled4.section`
1735
1821
  display: grid;
1736
1822
  gap: 14px;
@@ -1878,6 +1964,10 @@ var ErrorMessage = styled4.div`
1878
1964
  color: rgba(255, 145, 145, 0.96);
1879
1965
  font-size: 12px;
1880
1966
  `;
1967
+ var SuccessMessage = styled4.div`
1968
+ color: rgba(164, 255, 210, 0.96);
1969
+ font-size: 12px;
1970
+ `;
1881
1971
  var SubmitButton = styled4.button`
1882
1972
  justify-self: flex-start;
1883
1973
  border: none;
@@ -1888,12 +1978,17 @@ var SubmitButton = styled4.button`
1888
1978
  font-weight: 700;
1889
1979
  padding: 10px 14px;
1890
1980
  cursor: pointer;
1981
+
1982
+ &:disabled {
1983
+ cursor: default;
1984
+ opacity: 0.72;
1985
+ }
1891
1986
  `;
1892
1987
 
1893
- // src/components/chat-thread/components/pde-ai-result-summary-card.tsx
1988
+ // src/components/chat-thread/components/result-summary-card.tsx
1894
1989
  import styled5 from "@emotion/styled";
1895
1990
  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: [
1991
+ var ResultSummaryCard = ({ summary }) => /* @__PURE__ */ jsxs4(Card5, { "data-testid": "result-summary-card", "data-status": summary.status, children: [
1897
1992
  /* @__PURE__ */ jsx6(Eyebrow2, { children: summary.status }),
1898
1993
  /* @__PURE__ */ jsx6(Headline, { children: summary.headline }),
1899
1994
  /* @__PURE__ */ jsx6(Details, { children: summary.details.map((detail) => /* @__PURE__ */ jsx6(Detail, { children: detail }, detail)) })
@@ -1989,6 +2084,7 @@ var ImageViewer = ({ src, alt, onClose }) => {
1989
2084
  import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs5 } from "@emotion/react/jsx-runtime";
1990
2085
  var MARKDOWN_REMARK_PLUGINS = [remarkGfm, remarkMath];
1991
2086
  var MARKDOWN_REHYPE_PLUGINS = [rehypeKatex];
2087
+ var USER_MESSAGE_COLLAPSE_HEIGHT_PX = 120;
1992
2088
  var MARKDOWN_COMPONENTS = {
1993
2089
  table: ({ node: _node, ...props }) => /* @__PURE__ */ jsx8(TableWrapper, { children: /* @__PURE__ */ jsx8("table", { ...props }) })
1994
2090
  };
@@ -2001,10 +2097,101 @@ var renderMarkdownContent = (content) => /* @__PURE__ */ jsx8(
2001
2097
  children: content
2002
2098
  }
2003
2099
  );
2100
+ var renderPlainTextContent = (content) => content;
2101
+ var CollapseIcon = ({ expanded }) => /* @__PURE__ */ jsx8(
2102
+ "svg",
2103
+ {
2104
+ "aria-hidden": "true",
2105
+ width: "16",
2106
+ height: "16",
2107
+ viewBox: "0 0 16 16",
2108
+ fill: "none",
2109
+ xmlns: "http://www.w3.org/2000/svg",
2110
+ children: /* @__PURE__ */ jsx8(
2111
+ "path",
2112
+ {
2113
+ d: "M4 6l4 4 4-4",
2114
+ stroke: "currentColor",
2115
+ strokeWidth: "1.6",
2116
+ strokeLinecap: "round",
2117
+ strokeLinejoin: "round",
2118
+ transform: expanded ? "rotate(180 8 8)" : void 0
2119
+ }
2120
+ )
2121
+ }
2122
+ );
2123
+ var useUserMessageCollapse = ({
2124
+ blocks,
2125
+ displayedBlocks,
2126
+ displayedContent,
2127
+ enabled,
2128
+ freshContent,
2129
+ settledContent
2130
+ }) => {
2131
+ const [isCollapsible, setIsCollapsible] = useState3(false);
2132
+ const [isExpanded, setIsExpanded] = useState3(false);
2133
+ const [bodyStackElement, setBodyStackElement] = useState3(null);
2134
+ const syncCollapseState = useCallback2(
2135
+ (element) => {
2136
+ const nextCollapsible = enabled && (element?.scrollHeight ?? 0) > USER_MESSAGE_COLLAPSE_HEIGHT_PX;
2137
+ setIsCollapsible(nextCollapsible);
2138
+ if (!nextCollapsible) {
2139
+ setIsExpanded(false);
2140
+ }
2141
+ },
2142
+ [enabled]
2143
+ );
2144
+ const bodyStackRef = useCallback2(
2145
+ (node) => {
2146
+ setBodyStackElement(node);
2147
+ syncCollapseState(node);
2148
+ },
2149
+ [syncCollapseState]
2150
+ );
2151
+ useLayoutEffect(() => {
2152
+ if (!bodyStackElement) {
2153
+ return;
2154
+ }
2155
+ const frameId = requestAnimationFrame(() => {
2156
+ syncCollapseState(bodyStackElement);
2157
+ });
2158
+ return () => {
2159
+ cancelAnimationFrame(frameId);
2160
+ };
2161
+ }, [
2162
+ blocks,
2163
+ bodyStackElement,
2164
+ displayedBlocks,
2165
+ displayedContent,
2166
+ enabled,
2167
+ freshContent,
2168
+ settledContent,
2169
+ syncCollapseState
2170
+ ]);
2171
+ useLayoutEffect(() => {
2172
+ if (!bodyStackElement || !enabled || typeof ResizeObserver === "undefined") {
2173
+ return;
2174
+ }
2175
+ const observer = new ResizeObserver(() => {
2176
+ syncCollapseState(bodyStackElement);
2177
+ });
2178
+ observer.observe(bodyStackElement);
2179
+ return () => {
2180
+ observer.disconnect();
2181
+ };
2182
+ }, [bodyStackElement, enabled, syncCollapseState]);
2183
+ return {
2184
+ bodyStackRef,
2185
+ isCollapsed: isCollapsible && !isExpanded,
2186
+ isCollapsible,
2187
+ isExpanded,
2188
+ toggleExpanded: () => setIsExpanded((current) => !current)
2189
+ };
2190
+ };
2004
2191
  var createExecutionConfirmationContent = (proposal) => [
2005
2192
  "Execution confirmed",
2006
- `- Equation: ${proposal.equationName}`,
2007
- ...proposal.solverName ? [`- Solver: ${proposal.solverName}`] : [],
2193
+ `- Equation: ${proposal.resourceName}`,
2194
+ ...proposal.executorName ? [`- Solver: ${proposal.executorName}`] : [],
2008
2195
  `- Proposal ID: ${proposal.proposalId}`
2009
2196
  ].join("\n");
2010
2197
  var areChatAttachmentsEqual = (previousAttachments, nextAttachments) => {
@@ -2026,10 +2213,10 @@ var areParameterSummaryItemsEqual = (previousItems, nextItems) => previousItems.
2026
2213
  const nextItem = nextItems[index];
2027
2214
  return item.label === nextItem?.label && item.value === nextItem.value && item.fieldPath === nextItem.fieldPath;
2028
2215
  });
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(
2216
+ var areExecutionProposalsEqual = (previousProposal, nextProposal) => previousProposal.proposalId === nextProposal.proposalId && previousProposal.resourceKey === nextProposal.resourceKey && previousProposal.resourceName === nextProposal.resourceName && previousProposal.executorName === nextProposal.executorName && previousProposal.requiresConfirmation === nextProposal.requiresConfirmation && areParameterSummaryItemsEqual(previousProposal.parameterSummary, nextProposal.parameterSummary) && (previousProposal.warnings ?? []).length === (nextProposal.warnings ?? []).length && (previousProposal.warnings ?? []).every(
2030
2217
  (warning, index) => warning === nextProposal.warnings?.[index]
2031
2218
  );
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]);
2219
+ var areResultSummariesEqual = (previousSummary, nextSummary) => previousSummary.summaryId === nextSummary.summaryId && previousSummary.status === nextSummary.status && previousSummary.headline === nextSummary.headline && previousSummary.details.length === nextSummary.details.length && previousSummary.details.every((detail, index) => detail === nextSummary.details[index]);
2033
2220
  var areStringArraysEqual = (previousValues, nextValues) => previousValues.length === nextValues.length && previousValues.every((value, index) => value === nextValues[index]);
2034
2221
  var areQuestionAnswersEqual = (previousAnswer, nextAnswer) => {
2035
2222
  if (Array.isArray(previousAnswer) || Array.isArray(nextAnswer)) {
@@ -2070,7 +2257,7 @@ var arePlanQuestionsEqual = (previousQuestion, nextQuestion) => {
2070
2257
  return false;
2071
2258
  }
2072
2259
  };
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(
2260
+ var areQuestionnairesEqual = (previousQuestionnaire, nextQuestionnaire) => previousQuestionnaire.questionnaireId === nextQuestionnaire.questionnaireId && previousQuestionnaire.title === nextQuestionnaire.title && previousQuestionnaire.description === nextQuestionnaire.description && previousQuestionnaire.submitLabel === nextQuestionnaire.submitLabel && previousQuestionnaire.status === nextQuestionnaire.status && previousQuestionnaire.statusMessage === nextQuestionnaire.statusMessage && previousQuestionnaire.questions.length === nextQuestionnaire.questions.length && previousQuestionnaire.questions.every(
2074
2261
  (question, index) => arePlanQuestionsEqual(question, nextQuestionnaire.questions[index])
2075
2262
  ) && areQuestionAnswerMapsEqual(previousQuestionnaire.answers, nextQuestionnaire.answers);
2076
2263
  var areMessageBlocksEqual = (previousBlocks, nextBlocks) => {
@@ -2151,6 +2338,8 @@ var ChatMessageItemView = ({
2151
2338
  const canSubmitConfirmation = isPlanMode && typeof onConfirmationSubmit === "function";
2152
2339
  const canSubmitQuestionnaire = isPlanMode && typeof onQuestionnaireSubmit === "function";
2153
2340
  const shouldShowStreamingCaret = isAssistantStreaming && (!shouldRenderStructuredBlocks || hasTextContent);
2341
+ const isUserMessage = message.role === "user";
2342
+ const messageRenderMode = isUserMessage ? "plain-text" : "markdown";
2154
2343
  const timelineConsumedText = messageRenderOrder === "timeline" ? getTimelineConsumedText(blocks) : "";
2155
2344
  const hasConsumedTimelineText = timelineConsumedText.length > 0 && displayedContent.startsWith(timelineConsumedText);
2156
2345
  const timelineDisplayedContent = hasConsumedTimelineText ? displayedContent.slice(timelineConsumedText.length) : displayedContent;
@@ -2167,6 +2356,21 @@ var ChatMessageItemView = ({
2167
2356
  message,
2168
2357
  messageRenderOrder
2169
2358
  });
2359
+ const {
2360
+ bodyStackRef,
2361
+ isCollapsed: isUserMessageCollapsed,
2362
+ isCollapsible: isUserMessageCollapsible,
2363
+ isExpanded: isUserMessageExpanded,
2364
+ toggleExpanded: toggleUserMessageExpanded
2365
+ } = useUserMessageCollapse({
2366
+ blocks,
2367
+ displayedBlocks,
2368
+ displayedContent,
2369
+ enabled: isUserMessage,
2370
+ freshContent,
2371
+ settledContent
2372
+ });
2373
+ const renderMessageContent = (content) => messageRenderMode === "plain-text" ? renderPlainTextContent(content) : renderMarkdownContent(content);
2170
2374
  const renderChatMessageBlock = (block, index) => {
2171
2375
  switch (block.type) {
2172
2376
  case "markdown":
@@ -2175,17 +2379,18 @@ var ChatMessageItemView = ({
2175
2379
  {
2176
2380
  "data-testid": `chat-message-block-${index}`,
2177
2381
  "data-block-tone": "settled",
2178
- children: renderMarkdownContent(block.text)
2382
+ "data-render-mode": messageRenderMode,
2383
+ children: renderMessageContent(block.text)
2179
2384
  },
2180
2385
  `markdown-${index}`
2181
2386
  );
2182
2387
  case "notice":
2183
- return /* @__PURE__ */ jsx8(Fragment, { children: /* @__PURE__ */ jsx8(PDEAINoticeCard, { text: block.text, tone: block.tone }) }, `notice-${index}`);
2388
+ return /* @__PURE__ */ jsx8(Fragment, { children: /* @__PURE__ */ jsx8(NoticeCard, { text: block.text, tone: block.tone }) }, `notice-${index}`);
2184
2389
  case "parameter_summary":
2185
- return /* @__PURE__ */ jsx8(Fragment, { children: /* @__PURE__ */ jsx8(PDEAIParameterSummaryCard, { items: block.items }) }, `parameter-summary-${index}`);
2390
+ return /* @__PURE__ */ jsx8(Fragment, { children: /* @__PURE__ */ jsx8(ParameterSummaryCard, { items: block.items }) }, `parameter-summary-${index}`);
2186
2391
  case "confirmation_card":
2187
2392
  return /* @__PURE__ */ jsx8(Fragment, { children: /* @__PURE__ */ jsx8(
2188
- PDEAIExecutionConfirmationCard,
2393
+ ExecutionConfirmationCard,
2189
2394
  {
2190
2395
  proposal: block.proposal,
2191
2396
  interactive: isPlanMode,
@@ -2197,13 +2402,19 @@ var ChatMessageItemView = ({
2197
2402
  }
2198
2403
  ) }, `confirmation-card-${index}`);
2199
2404
  case "result_summary":
2200
- return /* @__PURE__ */ jsx8(Fragment, { children: /* @__PURE__ */ jsx8(PDEAIResultSummaryCard, { summary: block.summary }) }, `result-summary-${index}`);
2405
+ return /* @__PURE__ */ jsx8(Fragment, { children: /* @__PURE__ */ jsx8(ResultSummaryCard, { summary: block.summary }) }, `result-summary-${index}`);
2201
2406
  case "questionnaire":
2202
2407
  return /* @__PURE__ */ jsx8(Fragment, { children: /* @__PURE__ */ jsx8(
2203
- PDEAIQuestionnaireCard,
2408
+ QuestionnaireCard,
2204
2409
  {
2205
2410
  questionnaire: block.questionnaire,
2206
2411
  interactive: canSubmitQuestionnaire,
2412
+ labels: {
2413
+ submitting: labels.questionnaireSubmitting,
2414
+ submitted: labels.questionnaireSubmitted,
2415
+ validationPrefix: labels.questionnaireValidationPrefix,
2416
+ submitFailed: labels.questionnaireSubmitFailed
2417
+ },
2207
2418
  onSubmit: canSubmitQuestionnaire ? (submission) => onQuestionnaireSubmit({
2208
2419
  ...submission,
2209
2420
  sourceMessageId: message.id
@@ -2236,14 +2447,31 @@ var ChatMessageItemView = ({
2236
2447
  "data-testid": block.tone === "fresh" ? "chat-message-fresh-block" : "chat-message-settled-block",
2237
2448
  "data-block-tone": block.tone,
2238
2449
  "data-block-index": index,
2239
- children: renderMarkdownContent(block.content)
2450
+ "data-render-mode": messageRenderMode,
2451
+ children: renderMessageContent(block.content)
2240
2452
  },
2241
2453
  `${block.tone}-${index}`
2242
2454
  )),
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
2455
+ !textBlocks.some((block) => block.content) && !settledText && !freshText && Boolean(textContent) ? /* @__PURE__ */ jsx8(
2456
+ ContentBlock,
2457
+ {
2458
+ "data-testid": "chat-message-settled-block",
2459
+ "data-block-tone": "settled",
2460
+ "data-render-mode": messageRenderMode,
2461
+ children: renderMessageContent(textContent)
2462
+ }
2463
+ ) : null
2244
2464
  ] });
2245
2465
  };
2246
- const renderStaticTextSegment = (content) => /* @__PURE__ */ jsx8(ContentBlock, { "data-testid": "chat-message-settled-block", "data-block-tone": "settled", children: renderMarkdownContent(content) });
2466
+ const renderStaticTextSegment = (content) => /* @__PURE__ */ jsx8(
2467
+ ContentBlock,
2468
+ {
2469
+ "data-testid": "chat-message-settled-block",
2470
+ "data-block-tone": "settled",
2471
+ "data-render-mode": messageRenderMode,
2472
+ children: renderMessageContent(content)
2473
+ }
2474
+ );
2247
2475
  const bodySegments = (() => {
2248
2476
  if (!shouldRenderStructuredBlocks && hasTextContent) {
2249
2477
  return [{ type: "text" }];
@@ -2301,21 +2529,40 @@ var ChatMessageItemView = ({
2301
2529
  }
2302
2530
  ) : null,
2303
2531
  /* @__PURE__ */ jsx8(Role, { children: message.role === "user" ? labels.userRoleLabel : labels.assistantRoleLabel }),
2532
+ isUserMessageCollapsible ? /* @__PURE__ */ jsx8(
2533
+ CollapseToggle,
2534
+ {
2535
+ type: "button",
2536
+ "data-testid": "chat-message-collapse-toggle",
2537
+ "aria-label": isUserMessageExpanded ? labels.collapseMessageAriaLabel : labels.expandMessageAriaLabel,
2538
+ "aria-expanded": isUserMessageExpanded,
2539
+ onClick: toggleUserMessageExpanded,
2540
+ children: /* @__PURE__ */ jsx8(CollapseIcon, { expanded: isUserMessageExpanded })
2541
+ }
2542
+ ) : null,
2304
2543
  isStoppedAssistant ? /* @__PURE__ */ jsx8(StatusTag, { "data-testid": "chat-message-stopped-tag", children: labels.stoppedResponse }) : null
2305
2544
  ] }),
2306
2545
  /* @__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,
2546
+ shouldRenderStructuredBlocks || hasTextContent ? /* @__PURE__ */ jsx8(
2547
+ ContentStack,
2309
2548
  {
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,
2549
+ ref: bodyStackRef,
2550
+ "data-testid": "chat-message-body-stack",
2551
+ "data-collapsed": isUserMessageCollapsed,
2552
+ children: bodySegments.map((segment, index) => /* @__PURE__ */ jsx8(
2553
+ ContentSegment,
2554
+ {
2555
+ "data-testid": "chat-message-content-segment",
2556
+ children: segment.type === "block" ? renderChatMessageBlock(segment.block, segment.index) : segment.type === "text" ? segment.content !== void 0 ? segment.useTimelineSegmentation ? renderTextContent({
2557
+ content: segment.content,
2558
+ displayedBlocks: segment.displayedBlocks,
2559
+ useTimelineSegmentation: true
2560
+ }) : renderStaticTextSegment(segment.content) : renderTextContent() : renderStaticTextSegment(segment.content)
2561
+ },
2562
+ segment.type === "text" ? `text-${index}` : segment.type === "markdown" ? `markdown-${index}` : `${segment.block.type}-${segment.index}`
2563
+ ))
2564
+ }
2565
+ ) : null,
2319
2566
  attachments.length ? /* @__PURE__ */ jsx8(AttachmentGrid, { "data-testid": "chat-message-attachment-grid", children: attachments.map((attachment) => /* @__PURE__ */ jsx8(
2320
2567
  AttachmentButton,
2321
2568
  {
@@ -2400,6 +2647,29 @@ var StatusTag = styled7.span`
2400
2647
  letter-spacing: 0.02em;
2401
2648
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
2402
2649
  `;
2650
+ var CollapseToggle = styled7.button`
2651
+ margin-left: auto;
2652
+ width: 28px;
2653
+ height: 28px;
2654
+ display: inline-flex;
2655
+ align-items: center;
2656
+ justify-content: center;
2657
+ border: 1px solid rgba(255, 255, 255, 0.12);
2658
+ border-radius: 999px;
2659
+ background: rgba(255, 255, 255, 0.06);
2660
+ color: rgba(255, 255, 255, 0.72);
2661
+ cursor: pointer;
2662
+ transition:
2663
+ background 160ms ease-out,
2664
+ border-color 160ms ease-out,
2665
+ color 160ms ease-out;
2666
+
2667
+ &:hover {
2668
+ background: rgba(255, 255, 255, 0.1);
2669
+ border-color: rgba(255, 255, 255, 0.18);
2670
+ color: rgba(255, 255, 255, 0.92);
2671
+ }
2672
+ `;
2403
2673
  var Content = styled7.div`
2404
2674
  color: rgba(255, 255, 255, 0.92);
2405
2675
  line-height: 1.6;
@@ -2444,6 +2714,11 @@ var ContentStack = styled7.div`
2444
2714
  display: flex;
2445
2715
  flex-direction: column;
2446
2716
  gap: 16px;
2717
+
2718
+ &[data-collapsed='true'] {
2719
+ max-height: ${USER_MESSAGE_COLLAPSE_HEIGHT_PX}px;
2720
+ overflow: hidden;
2721
+ }
2447
2722
  `;
2448
2723
  var ContentSegment = styled7.div`
2449
2724
  min-width: 0;
@@ -2457,6 +2732,11 @@ var ContentBlock = styled7.div`
2457
2732
  margin-top: 16px;
2458
2733
  }
2459
2734
 
2735
+ &[data-render-mode='plain-text'] {
2736
+ white-space: pre-wrap;
2737
+ overflow-wrap: anywhere;
2738
+ }
2739
+
2460
2740
  &[data-block-tone='fresh'] {
2461
2741
  opacity: 0.85;
2462
2742
  filter: brightness(0.82) saturate(0.88);
@@ -2718,7 +2998,7 @@ var ChatThreadView = ({
2718
2998
  const latestUserMessageRef = useRef4(null);
2719
2999
  const reservedSpaceFrameRef = useRef4(null);
2720
3000
  const [latestTurnMinHeight, setLatestTurnMinHeight] = useState4(0);
2721
- const measureLatestTurnMinHeight = useCallback2(() => {
3001
+ const measureLatestTurnMinHeight = useCallback3(() => {
2722
3002
  const container = containerRef.current;
2723
3003
  if (!container)
2724
3004
  return;
@@ -2728,7 +3008,7 @@ var ChatThreadView = ({
2728
3008
  const nextMinHeight = Math.max(0, container.clientHeight - paddingTop - paddingBottom);
2729
3009
  setLatestTurnMinHeight((current) => current === nextMinHeight ? current : nextMinHeight);
2730
3010
  }, []);
2731
- const scrollLatestUserMessageToTop = useCallback2(() => {
3011
+ const scrollLatestUserMessageToTop = useCallback3(() => {
2732
3012
  const container = containerRef.current;
2733
3013
  const target = latestUserMessageRef.current;
2734
3014
  if (!container || !target)
@@ -2748,7 +3028,7 @@ var ChatThreadView = ({
2748
3028
  }
2749
3029
  container.scrollTop = nextScrollTop;
2750
3030
  }, []);
2751
- useLayoutEffect(() => {
3031
+ useLayoutEffect2(() => {
2752
3032
  if (reservedSpaceFrameRef.current !== null) {
2753
3033
  window.cancelAnimationFrame(reservedSpaceFrameRef.current);
2754
3034
  reservedSpaceFrameRef.current = null;
@@ -2777,7 +3057,7 @@ var ChatThreadView = ({
2777
3057
  }
2778
3058
  };
2779
3059
  }, [latestUserMessageId, measureLatestTurnMinHeight, scrollLatestUserMessageToTop]);
2780
- useLayoutEffect(() => {
3060
+ useLayoutEffect2(() => {
2781
3061
  if (!latestUserMessageId)
2782
3062
  return;
2783
3063
  const handleResize = () => {
@@ -2864,15 +3144,40 @@ var ChatThread = () => {
2864
3144
  const error = useChatStore((s) => s.errorBySession[s.activeSessionId ?? ""]);
2865
3145
  const updateQA = useChatStore((s) => s.updateQuestionnaireAnswers);
2866
3146
  const clearSessionError = useChatStore((s) => s.clearSessionError);
2867
- const { sendRef, retryRef, renderMessageBlock, labels } = useChatContext();
2868
- const handleRetry = useCallback2(() => {
3147
+ const {
3148
+ sendRef,
3149
+ retryRef,
3150
+ renderMessageBlock,
3151
+ handleQuestionnaireSubmit: customQuestionnaireSubmit,
3152
+ handleConfirmationSubmit: customConfirmationSubmit,
3153
+ labels
3154
+ } = useChatContext();
3155
+ const handleRetry = useCallback3(() => {
2869
3156
  if (!activeSessionId)
2870
3157
  return;
2871
3158
  clearSessionError(activeSessionId);
2872
3159
  void retryRef.current();
2873
3160
  }, [activeSessionId, clearSessionError, retryRef]);
2874
- const handleQuestionnaireSubmit = useCallback2(
2875
- (submission) => {
3161
+ const handleQuestionnaireSubmit = useCallback3(
3162
+ async (submission) => {
3163
+ if (customQuestionnaireSubmit) {
3164
+ const handled = await customQuestionnaireSubmit(submission, {
3165
+ sessionId: activeSessionId ?? void 0,
3166
+ mode: activeSessionMode
3167
+ });
3168
+ if (handled !== false) {
3169
+ if (activeSessionId && submission.sourceMessageId) {
3170
+ updateQA(
3171
+ activeSessionId,
3172
+ submission.sourceMessageId,
3173
+ submission.questionnaireId,
3174
+ submission.answers
3175
+ );
3176
+ }
3177
+ return;
3178
+ }
3179
+ }
3180
+ await sendRef.current(submission.content);
2876
3181
  if (activeSessionId && submission.sourceMessageId) {
2877
3182
  updateQA(
2878
3183
  activeSessionId,
@@ -2881,15 +3186,23 @@ var ChatThread = () => {
2881
3186
  submission.answers
2882
3187
  );
2883
3188
  }
2884
- void sendRef.current(submission.content);
2885
3189
  },
2886
- [activeSessionId, updateQA, sendRef]
3190
+ [activeSessionId, activeSessionMode, updateQA, sendRef, customQuestionnaireSubmit]
2887
3191
  );
2888
- const handleConfirmationSubmit = useCallback2(
2889
- (submission) => {
2890
- void sendRef.current(submission.content);
3192
+ const handleConfirmation = useCallback3(
3193
+ async (submission) => {
3194
+ if (customConfirmationSubmit) {
3195
+ const handled = await customConfirmationSubmit(submission, {
3196
+ sessionId: activeSessionId ?? void 0,
3197
+ mode: activeSessionMode
3198
+ });
3199
+ if (handled !== false) {
3200
+ return;
3201
+ }
3202
+ }
3203
+ await sendRef.current(submission.content);
2891
3204
  },
2892
- [sendRef]
3205
+ [activeSessionId, activeSessionMode, sendRef, customConfirmationSubmit]
2893
3206
  );
2894
3207
  if (!hasSessions || messages.length === 0 && !streamingMessage) {
2895
3208
  return /* @__PURE__ */ jsx10(ChatThreadEmptyState, {});
@@ -2903,7 +3216,7 @@ var ChatThread = () => {
2903
3216
  error,
2904
3217
  retryButtonLabel: labels.retryButton,
2905
3218
  onRetry: handleRetry,
2906
- onConfirmationSubmit: handleConfirmationSubmit,
3219
+ onConfirmationSubmit: handleConfirmation,
2907
3220
  onQuestionnaireSubmit: handleQuestionnaireSubmit,
2908
3221
  renderMessageBlock
2909
3222
  }
@@ -2917,6 +3230,7 @@ var Container = styled9.div`
2917
3230
  min-height: 0;
2918
3231
  overflow: auto;
2919
3232
  padding: 24px;
3233
+ margin-bottom: 24px;
2920
3234
  overscroll-behavior: contain;
2921
3235
 
2922
3236
  &::-webkit-scrollbar {
@@ -2970,7 +3284,7 @@ var RetryButton = styled9.button`
2970
3284
  `;
2971
3285
 
2972
3286
  // src/components/chat-composer/index.tsx
2973
- import { useEffect as useEffect6, useRef as useRef7 } from "react";
3287
+ import { useEffect as useEffect6, useLayoutEffect as useLayoutEffect3, useRef as useRef7, useState as useState8 } from "react";
2974
3288
  import styled14 from "@emotion/styled";
2975
3289
 
2976
3290
  // src/components/chat-composer/lib/chat-composer.ts
@@ -3082,7 +3396,7 @@ var resolveSendSession = ({
3082
3396
  };
3083
3397
 
3084
3398
  // 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";
3399
+ import { useCallback as useCallback4, useEffect as useEffect5, useRef as useRef6, useState as useState6 } from "react";
3086
3400
 
3087
3401
  // src/components/chat-composer/hooks/use-composer-attachments.ts
3088
3402
  import { useEffect as useEffect4, useRef as useRef5, useState as useState5 } from "react";
@@ -3220,7 +3534,7 @@ var useChatComposer = () => {
3220
3534
  const [availableModels, setAvailableModels] = useState6([]);
3221
3535
  const [isModelsLoading, setIsModelsLoading] = useState6(true);
3222
3536
  const [isModelsError, setIsModelsError] = useState6(false);
3223
- const fetchModels = useCallback3(async () => {
3537
+ const fetchModels = useCallback4(async () => {
3224
3538
  setIsModelsLoading(true);
3225
3539
  setIsModelsError(false);
3226
3540
  try {
@@ -3275,7 +3589,7 @@ var useChatComposer = () => {
3275
3589
  stopRequestRef.current.timeoutId = null;
3276
3590
  }
3277
3591
  };
3278
- const clearStopRequest = useCallback3((sessionId) => {
3592
+ const clearStopRequest = useCallback4((sessionId) => {
3279
3593
  if (!stopRequestRef.current)
3280
3594
  return;
3281
3595
  if (sessionId && stopRequestRef.current.sessionId !== sessionId)
@@ -3283,7 +3597,7 @@ var useChatComposer = () => {
3283
3597
  clearStopTimeout(sessionId);
3284
3598
  stopRequestRef.current = null;
3285
3599
  }, []);
3286
- const finalizeStop = useCallback3(
3600
+ const finalizeStop = useCallback4(
3287
3601
  (sessionId) => {
3288
3602
  if (stopRequestRef.current?.sessionId === sessionId) {
3289
3603
  if (stopRequestRef.current.finalized)
@@ -3298,7 +3612,7 @@ var useChatComposer = () => {
3298
3612
  },
3299
3613
  [clearStopRequest, finalizeStoppedStreamingMessage]
3300
3614
  );
3301
- const runStream = useCallback3(
3615
+ const runStream = useCallback4(
3302
3616
  async ({
3303
3617
  localSessionId,
3304
3618
  sessionId,
@@ -3387,7 +3701,7 @@ var useChatComposer = () => {
3387
3701
  setSessionError
3388
3702
  ]
3389
3703
  );
3390
- const send = useCallback3(
3704
+ const send = useCallback4(
3391
3705
  async (contentOverride) => {
3392
3706
  const content = (contentOverride ?? value).trim();
3393
3707
  const hasText = Boolean(content);
@@ -3996,6 +4310,27 @@ var StopSpinner = styled13.span`
3996
4310
 
3997
4311
  // src/components/chat-composer/index.tsx
3998
4312
  import { jsx as jsx15, jsxs as jsxs10 } from "@emotion/react/jsx-runtime";
4313
+ var CHAT_COMPOSER_LINE_HEIGHT_PX = 20;
4314
+ var CHAT_COMPOSER_MAX_ROWS = 7;
4315
+ var CHAT_COMPOSER_PADDING_TOP_PX = 8;
4316
+ var CHAT_COMPOSER_PADDING_BOTTOM_PX = 12;
4317
+ var CHAT_COMPOSER_PADDING_BLOCK_PX = CHAT_COMPOSER_PADDING_TOP_PX + CHAT_COMPOSER_PADDING_BOTTOM_PX;
4318
+ var CHAT_COMPOSER_MIN_ROWS = 4;
4319
+ var CHAT_COMPOSER_EXPANDED_MAX_ROWS = 60;
4320
+ var CHAT_COMPOSER_EXPANDED_MAX_VIEWPORT_RATIO = 0.7;
4321
+ var CHAT_COMPOSER_EXPANDED_RESERVED_SPACE_PX = 96;
4322
+ var CHAT_COMPOSER_MAX_HEIGHT_PX = CHAT_COMPOSER_MAX_ROWS * CHAT_COMPOSER_LINE_HEIGHT_PX + CHAT_COMPOSER_PADDING_BLOCK_PX;
4323
+ var CHAT_COMPOSER_EXPANDED_ROWS_HEIGHT_PX = CHAT_COMPOSER_EXPANDED_MAX_ROWS * CHAT_COMPOSER_LINE_HEIGHT_PX + CHAT_COMPOSER_PADDING_BLOCK_PX;
4324
+ var getExpandedComposerHeightPx = () => {
4325
+ if (typeof window === "undefined") {
4326
+ return CHAT_COMPOSER_EXPANDED_ROWS_HEIGHT_PX;
4327
+ }
4328
+ const viewportLimitedHeight = window.innerHeight * CHAT_COMPOSER_EXPANDED_MAX_VIEWPORT_RATIO - CHAT_COMPOSER_EXPANDED_RESERVED_SPACE_PX;
4329
+ return Math.max(
4330
+ CHAT_COMPOSER_MAX_HEIGHT_PX,
4331
+ Math.floor(Math.min(CHAT_COMPOSER_EXPANDED_ROWS_HEIGHT_PX, viewportLimitedHeight))
4332
+ );
4333
+ };
3999
4334
  var PlusIcon = () => /* @__PURE__ */ jsx15(
4000
4335
  "svg",
4001
4336
  {
@@ -4008,6 +4343,36 @@ var PlusIcon = () => /* @__PURE__ */ jsx15(
4008
4343
  children: /* @__PURE__ */ jsx15("path", { d: "M8 3v10M3 8h10", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round" })
4009
4344
  }
4010
4345
  );
4346
+ var ComposerExpandIcon = ({ expanded }) => /* @__PURE__ */ jsx15(
4347
+ "svg",
4348
+ {
4349
+ "aria-hidden": "true",
4350
+ width: "16",
4351
+ height: "16",
4352
+ viewBox: "0 0 16 16",
4353
+ fill: "none",
4354
+ xmlns: "http://www.w3.org/2000/svg",
4355
+ children: expanded ? /* @__PURE__ */ jsx15(
4356
+ "path",
4357
+ {
4358
+ d: "M14 6h-4V2M10 6l4-4M2 10h4v4M6 10l-4 4",
4359
+ stroke: "currentColor",
4360
+ strokeWidth: "1.5",
4361
+ strokeLinecap: "round",
4362
+ strokeLinejoin: "round"
4363
+ }
4364
+ ) : /* @__PURE__ */ jsx15(
4365
+ "path",
4366
+ {
4367
+ d: "M10 2h4v4M14 2L9 7M6 14H2v-4M2 14l5-5",
4368
+ stroke: "currentColor",
4369
+ strokeWidth: "1.5",
4370
+ strokeLinecap: "round",
4371
+ strokeLinejoin: "round"
4372
+ }
4373
+ )
4374
+ }
4375
+ );
4011
4376
  var ChatComposerView = ({
4012
4377
  value,
4013
4378
  placeholder,
@@ -4024,6 +4389,8 @@ var ChatComposerView = ({
4024
4389
  isStopping,
4025
4390
  enableImageAttachments,
4026
4391
  modeLabels,
4392
+ expandComposerAriaLabel,
4393
+ collapseComposerAriaLabel,
4027
4394
  onValueChange,
4028
4395
  onPickImages,
4029
4396
  onPasteImages,
@@ -4035,6 +4402,9 @@ var ChatComposerView = ({
4035
4402
  onSend
4036
4403
  }) => {
4037
4404
  const imageInputRef = useRef7(null);
4405
+ const inputRef = useRef7(null);
4406
+ const [isComposerExpandable, setIsComposerExpandable] = useState8(false);
4407
+ const [isComposerExpanded, setIsComposerExpanded] = useState8(false);
4038
4408
  const canSend = canSendChatMessage({
4039
4409
  value,
4040
4410
  attachmentCount: attachments.length,
@@ -4042,6 +4412,26 @@ var ChatComposerView = ({
4042
4412
  isModelsError,
4043
4413
  hasModels
4044
4414
  });
4415
+ useLayoutEffect3(() => {
4416
+ const element = inputRef.current;
4417
+ if (!element) {
4418
+ return;
4419
+ }
4420
+ if (!isComposerExpanded) {
4421
+ element.style.height = "0px";
4422
+ }
4423
+ const scrollHeight = element.scrollHeight;
4424
+ const nextExpandable = scrollHeight > CHAT_COMPOSER_MAX_HEIGHT_PX;
4425
+ const expandedHeight = getExpandedComposerHeightPx();
4426
+ const nextHeight = isComposerExpanded ? expandedHeight : Math.min(scrollHeight, CHAT_COMPOSER_MAX_HEIGHT_PX);
4427
+ setIsComposerExpandable(nextExpandable);
4428
+ element.style.height = `${nextHeight}px`;
4429
+ element.style.overflowY = !isComposerExpanded && scrollHeight > CHAT_COMPOSER_MAX_HEIGHT_PX ? "auto" : "hidden";
4430
+ }, [isComposerExpanded, value]);
4431
+ const handleSend = async () => {
4432
+ setIsComposerExpanded(false);
4433
+ await onSend();
4434
+ };
4045
4435
  const handleKeyDown = (event) => {
4046
4436
  if (shouldStopChatComposer({ key: event.key, shiftKey: event.shiftKey, isStreaming })) {
4047
4437
  event.preventDefault();
@@ -4051,7 +4441,7 @@ var ChatComposerView = ({
4051
4441
  if (!shouldSubmitChatComposer({ key: event.key, shiftKey: event.shiftKey, canSend }))
4052
4442
  return;
4053
4443
  event.preventDefault();
4054
- void onSend();
4444
+ void handleSend();
4055
4445
  };
4056
4446
  const handlePickImages = (event) => {
4057
4447
  if (event.target.files?.length)
@@ -4086,60 +4476,77 @@ var ChatComposerView = ({
4086
4476
  }
4087
4477
  ),
4088
4478
  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,
4479
+ /* @__PURE__ */ jsxs10(InputArea, { "data-testid": "chat-composer-input-area", children: [
4480
+ isComposerExpanded || isComposerExpandable ? /* @__PURE__ */ jsx15(
4481
+ ComposerExpandButton,
4103
4482
  {
4104
4483
  type: "button",
4105
- "data-testid": "chat-composer-attach-image",
4106
- "aria-label": "Attach image",
4107
- onClick: () => imageInputRef.current?.click(),
4108
- children: /* @__PURE__ */ jsx15(PlusIcon, {})
4484
+ "data-testid": "chat-composer-expand-toggle",
4485
+ "aria-label": isComposerExpanded ? collapseComposerAriaLabel : expandComposerAriaLabel,
4486
+ "aria-expanded": isComposerExpanded,
4487
+ onClick: () => setIsComposerExpanded((current) => !current),
4488
+ children: /* @__PURE__ */ jsx15(ComposerExpandIcon, { expanded: isComposerExpanded })
4109
4489
  }
4110
4490
  ) : null,
4111
4491
  /* @__PURE__ */ jsx15(
4112
- ChatModeControl,
4492
+ Input,
4113
4493
  {
4114
- value: selectedMode,
4115
- disabled: isStreaming,
4116
- labels: modeLabels,
4117
- onChange: onSelectedModeChange
4494
+ ref: inputRef,
4495
+ "data-testid": "chat-composer-input",
4496
+ "data-expanded": isComposerExpanded,
4497
+ value,
4498
+ onChange: (event) => onValueChange(event.target.value),
4499
+ onKeyDown: handleKeyDown,
4500
+ onPaste: enableImageAttachments ? handlePaste : void 0,
4501
+ placeholder
4118
4502
  }
4119
- ),
4120
- /* @__PURE__ */ jsx15(
4121
- ChatModelControl,
4122
- {
4123
- selectedModel,
4124
- availableModels,
4125
- isModelsLoading,
4126
- isModelsError,
4127
- hasModels,
4128
- onSelectedModelChange,
4129
- onReloadModels
4130
- }
4131
- ),
4132
- /* @__PURE__ */ jsx15(
4133
- ChatSendActions,
4503
+ )
4504
+ ] }),
4505
+ /* @__PURE__ */ jsxs10(Footer, { children: [
4506
+ /* @__PURE__ */ jsx15(LeadingActions, { "data-testid": "chat-composer-leading-actions", children: enableImageAttachments ? /* @__PURE__ */ jsx15(
4507
+ AttachButton,
4134
4508
  {
4135
- canSend,
4136
- isStreaming,
4137
- isStopping,
4138
- onStop,
4139
- onSend
4509
+ type: "button",
4510
+ "data-testid": "chat-composer-attach-image",
4511
+ "aria-label": "Attach image",
4512
+ onClick: () => imageInputRef.current?.click(),
4513
+ children: /* @__PURE__ */ jsx15(PlusIcon, {})
4140
4514
  }
4141
- )
4142
- ] }) })
4515
+ ) : null }),
4516
+ /* @__PURE__ */ jsxs10(TrailingActions, { "data-testid": "chat-composer-trailing-actions", children: [
4517
+ /* @__PURE__ */ jsx15(
4518
+ ChatModeControl,
4519
+ {
4520
+ value: selectedMode,
4521
+ disabled: isStreaming,
4522
+ labels: modeLabels,
4523
+ onChange: onSelectedModeChange
4524
+ }
4525
+ ),
4526
+ /* @__PURE__ */ jsx15(
4527
+ ChatModelControl,
4528
+ {
4529
+ selectedModel,
4530
+ availableModels,
4531
+ isModelsLoading,
4532
+ isModelsError,
4533
+ hasModels,
4534
+ onSelectedModelChange,
4535
+ onReloadModels
4536
+ }
4537
+ ),
4538
+ /* @__PURE__ */ jsx15(
4539
+ ChatSendActions,
4540
+ {
4541
+ canSend,
4542
+ isStreaming,
4543
+ isStopping,
4544
+ onStop,
4545
+ onSend: handleSend
4546
+ }
4547
+ )
4548
+ ] })
4549
+ ] })
4143
4550
  ] }) });
4144
4551
  };
4145
4552
  var ChatComposer = () => {
@@ -4175,6 +4582,8 @@ var ChatComposer = () => {
4175
4582
  isStopping: state.isStopping,
4176
4583
  enableImageAttachments,
4177
4584
  modeLabels,
4585
+ expandComposerAriaLabel: labels.expandComposerAriaLabel,
4586
+ collapseComposerAriaLabel: labels.collapseComposerAriaLabel,
4178
4587
  onValueChange: actions.setValue,
4179
4588
  onPickImages: actions.pickImages,
4180
4589
  onPasteImages: actions.pasteImages,
@@ -4191,6 +4600,16 @@ var Container2 = styled14.div`
4191
4600
  padding: 0 16px 16px;
4192
4601
  `;
4193
4602
  var Surface = styled14.div`
4603
+ display: grid;
4604
+ grid-template-columns: minmax(0, 1fr);
4605
+ grid-template-areas:
4606
+ 'attachments'
4607
+ 'notice'
4608
+ 'input'
4609
+ 'footer';
4610
+ width: 100%;
4611
+ max-width: 760px;
4612
+ margin: 0 auto;
4194
4613
  background: var(--border-color);
4195
4614
  border-radius: 20px;
4196
4615
  border: 1px solid var(--border-hover);
@@ -4200,6 +4619,7 @@ var Surface = styled14.div`
4200
4619
  backdrop-filter: blur(10px);
4201
4620
  `;
4202
4621
  var AttachmentNotice = styled14.div`
4622
+ grid-area: notice;
4203
4623
  margin: 10px 12px 0;
4204
4624
  padding: 8px 10px;
4205
4625
  border-radius: 10px;
@@ -4209,19 +4629,45 @@ var AttachmentNotice = styled14.div`
4209
4629
  font-size: 12px;
4210
4630
  line-height: 1.4;
4211
4631
  `;
4632
+ var InputArea = styled14.div`
4633
+ grid-area: input;
4634
+ position: relative;
4635
+ `;
4212
4636
  var Input = styled14.textarea`
4637
+ --textarea-line-height: ${CHAT_COMPOSER_LINE_HEIGHT_PX}px;
4638
+ --textarea-min-rows: ${CHAT_COMPOSER_MIN_ROWS};
4639
+ --textarea-max-rows: ${CHAT_COMPOSER_MAX_ROWS};
4640
+ --textarea-expanded-max-rows: ${CHAT_COMPOSER_EXPANDED_MAX_ROWS};
4641
+ --textarea-padding-top: ${CHAT_COMPOSER_PADDING_TOP_PX}px;
4642
+ --textarea-padding-bottom: ${CHAT_COMPOSER_PADDING_BOTTOM_PX}px;
4643
+ --textarea-padding-block: calc(var(--textarea-padding-top) + var(--textarea-padding-bottom));
4644
+ --textarea-max-height: calc(
4645
+ var(--textarea-max-rows) * var(--textarea-line-height) + var(--textarea-padding-block)
4646
+ );
4647
+ --textarea-expanded-max-height: min(
4648
+ calc(
4649
+ var(--textarea-expanded-max-rows) * var(--textarea-line-height) +
4650
+ var(--textarea-padding-block)
4651
+ ),
4652
+ calc(70vh - ${CHAT_COMPOSER_EXPANDED_RESERVED_SPACE_PX}px)
4653
+ );
4213
4654
  width: 100%;
4214
- min-height: 96px;
4655
+ min-height: calc(
4656
+ var(--textarea-min-rows) * var(--textarea-line-height) + var(--textarea-padding-block)
4657
+ );
4658
+ max-height: var(--textarea-max-height);
4659
+ box-sizing: border-box;
4215
4660
  resize: none;
4216
4661
  appearance: none;
4217
4662
  border: 0;
4218
4663
  outline: 0;
4219
4664
  background: transparent;
4220
- padding: 8px 12px 12px 12px;
4665
+ padding: var(--textarea-padding-top) 44px var(--textarea-padding-bottom) 12px;
4221
4666
  font-weight: 400;
4222
4667
  font-size: 14px;
4223
4668
  color: var(--text-primary);
4224
- line-height: 20px;
4669
+ line-height: var(--textarea-line-height);
4670
+ overflow-y: hidden;
4225
4671
 
4226
4672
  &::placeholder {
4227
4673
  color: var(--text-secondary);
@@ -4230,19 +4676,50 @@ var Input = styled14.textarea`
4230
4676
  &::-webkit-resizer {
4231
4677
  display: none;
4232
4678
  }
4679
+
4680
+ &[data-expanded='true'] {
4681
+ max-height: var(--textarea-expanded-max-height);
4682
+ }
4683
+ `;
4684
+ var ComposerExpandButton = styled14.button`
4685
+ position: absolute;
4686
+ top: 8px;
4687
+ right: 10px;
4688
+ width: 28px;
4689
+ height: 28px;
4690
+ display: grid;
4691
+ place-items: center;
4692
+ border: none;
4693
+ border-radius: 999px;
4694
+ background: transparent;
4695
+ color: rgba(255, 255, 255, 0.72);
4696
+ cursor: pointer;
4697
+ z-index: 1;
4698
+
4699
+ &:hover {
4700
+ background: rgba(255, 255, 255, 0.08);
4701
+ color: rgba(255, 255, 255, 0.92);
4702
+ }
4233
4703
  `;
4234
4704
  var Footer = styled14.div`
4235
- display: flex;
4705
+ grid-area: footer;
4706
+ display: grid;
4707
+ grid-template-columns: minmax(0, 1fr) auto;
4236
4708
  align-items: flex-end;
4237
- justify-content: stretch;
4238
4709
  gap: 16px;
4239
4710
  padding: 0 14px 14px;
4240
4711
  `;
4241
- var Actions2 = styled14.div`
4712
+ var LeadingActions = styled14.div`
4713
+ display: flex;
4714
+ align-items: center;
4715
+ justify-content: flex-start;
4716
+ gap: 8px;
4717
+ min-width: 0;
4718
+ `;
4719
+ var TrailingActions = styled14.div`
4242
4720
  display: flex;
4243
4721
  align-items: center;
4244
4722
  flex-wrap: wrap;
4245
- width: 100%;
4246
4723
  min-width: 0;
4247
4724
  justify-content: flex-end;
4248
4725
  gap: 8px;