@case-framework/survey-assistant 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/dist/{authoring-references-DYuOAdVx.mjs → authoring-references-yyYC4Ydi.mjs} +9 -3
  2. package/dist/authoring-references-yyYC4Ydi.mjs.map +1 -0
  3. package/dist/{capabilities-DlC1l6qy.d.mts → capabilities-CnsVuJ1k.d.mts} +12 -4
  4. package/dist/capabilities-CnsVuJ1k.d.mts.map +1 -0
  5. package/dist/capabilities-default.d.mts +1 -1
  6. package/dist/capabilities-default.d.mts.map +1 -1
  7. package/dist/capabilities-default.mjs +38 -4
  8. package/dist/capabilities-default.mjs.map +1 -1
  9. package/dist/{constants-D-5m_KGB.d.mts → constants-DrGZXtQf.d.mts} +12 -7
  10. package/dist/constants-DrGZXtQf.d.mts.map +1 -0
  11. package/dist/{controller-proxy-0WJZUZYx.mjs → controller-proxy-CpkQRezb.mjs} +3 -2
  12. package/dist/controller-proxy-CpkQRezb.mjs.map +1 -0
  13. package/dist/{controller-proxy-Hkuub9dt.d.mts → controller-proxy-DmBdzqKs.d.mts} +5 -2
  14. package/dist/controller-proxy-DmBdzqKs.d.mts.map +1 -0
  15. package/dist/{digest-BvXpeWcR.d.mts → digest-Dyjch_sC.d.mts} +2 -2
  16. package/dist/{digest-BvXpeWcR.d.mts.map → digest-Dyjch_sC.d.mts.map} +1 -1
  17. package/dist/digest.d.mts +1 -1
  18. package/dist/{engine-0AJrv0hI.mjs → engine-CWTuN3o2.mjs} +866 -663
  19. package/dist/engine-CWTuN3o2.mjs.map +1 -0
  20. package/dist/engine.d.mts +3 -3
  21. package/dist/engine.mjs +3 -3
  22. package/dist/fair-guidance-CEdJYccK.mjs +21 -0
  23. package/dist/fair-guidance-CEdJYccK.mjs.map +1 -0
  24. package/dist/form-authoring-DCd9XfXf.mjs +155 -0
  25. package/dist/form-authoring-DCd9XfXf.mjs.map +1 -0
  26. package/dist/{index-JXH-t2EJ.d.mts → index-Bo4dHAo8.d.mts} +68 -6
  27. package/dist/index-Bo4dHAo8.d.mts.map +1 -0
  28. package/dist/{index-MEOL9_Gr.d.mts → index-D4OitUg4.d.mts} +5 -430
  29. package/dist/index-D4OitUg4.d.mts.map +1 -0
  30. package/dist/{index-BcyfBeDj.d.mts → index-DZk20e3k.d.mts} +44 -22
  31. package/dist/index-DZk20e3k.d.mts.map +1 -0
  32. package/dist/{index-CneD8ibB.d.mts → index-Dxgg0-D_.d.mts} +12 -3
  33. package/dist/index-Dxgg0-D_.d.mts.map +1 -0
  34. package/dist/lifecycle-DWv5CiSu.d.mts +523 -0
  35. package/dist/lifecycle-DWv5CiSu.d.mts.map +1 -0
  36. package/dist/{memory-thread-repository-1ocPOda8.mjs → memory-thread-repository-acbz6HZJ.mjs} +181 -26
  37. package/dist/memory-thread-repository-acbz6HZJ.mjs.map +1 -0
  38. package/dist/{protocol-DpCYmCjr.mjs → protocol-DATvkr1S.mjs} +125 -6
  39. package/dist/protocol-DATvkr1S.mjs.map +1 -0
  40. package/dist/protocol.d.mts +4 -3
  41. package/dist/protocol.mjs +2 -2
  42. package/dist/{react-DXK5zWNo.mjs → react-Br5YE8H-.mjs} +132 -12
  43. package/dist/react-Br5YE8H-.mjs.map +1 -0
  44. package/dist/react-integration.d.mts +1 -1
  45. package/dist/react-integration.mjs +1 -1
  46. package/dist/react.d.mts +2 -2
  47. package/dist/react.mjs +2 -2
  48. package/dist/references/assistant-operations.md +13 -18
  49. package/dist/references/embedded-forms.md +41 -2
  50. package/dist/references/expressions.md +17 -9
  51. package/dist/references/fair-by-design.md +80 -0
  52. package/dist/references/item-types.md +19 -3
  53. package/dist/references/localization.md +7 -4
  54. package/dist/references/source-material-surveys.md +6 -4
  55. package/dist/{request-context-BBzU3VdG.mjs → request-context-BEjnaaFr.mjs} +10 -5
  56. package/dist/request-context-BEjnaaFr.mjs.map +1 -0
  57. package/dist/server-agent.d.mts +44 -22
  58. package/dist/server-agent.d.mts.map +1 -1
  59. package/dist/server-agent.mjs +71 -14
  60. package/dist/server-agent.mjs.map +1 -1
  61. package/dist/server-runtime.d.mts +22 -11
  62. package/dist/server-runtime.d.mts.map +1 -1
  63. package/dist/server-runtime.mjs +47 -19
  64. package/dist/server-runtime.mjs.map +1 -1
  65. package/dist/server-tasks.d.mts +1 -1
  66. package/dist/server-tasks.mjs +6 -4
  67. package/dist/server-tasks.mjs.map +1 -1
  68. package/dist/server-tools.d.mts +1 -1
  69. package/dist/server-tools.mjs +1 -1
  70. package/dist/server.d.mts +16 -7
  71. package/dist/server.d.mts.map +1 -1
  72. package/dist/server.mjs +4 -4
  73. package/dist/storage-postgres.d.mts +1 -1
  74. package/dist/{tasks-DcfQNJwO.d.mts → tasks-DQgL449A.d.mts} +8 -4
  75. package/dist/tasks-DQgL449A.d.mts.map +1 -0
  76. package/dist/{thread-documents-BlxNjc7S.d.mts → thread-documents-Dvo6XS7o.d.mts} +1 -1
  77. package/dist/{thread-documents-BlxNjc7S.d.mts.map → thread-documents-Dvo6XS7o.d.mts.map} +1 -1
  78. package/dist/{thread-handlers-uddXwQjp.d.mts → thread-handlers-CeYl45xG.d.mts} +9 -4
  79. package/dist/thread-handlers-CeYl45xG.d.mts.map +1 -0
  80. package/dist/{tools-wgLen05n.mjs → tools-Dg9HZrYU.mjs} +404 -78
  81. package/dist/tools-Dg9HZrYU.mjs.map +1 -0
  82. package/dist/ui.css +2 -2
  83. package/dist/ui.d.mts +94 -10
  84. package/dist/ui.d.mts.map +1 -1
  85. package/dist/ui.mjs +343 -112
  86. package/dist/ui.mjs.map +1 -1
  87. package/docs/integration.md +74 -6
  88. package/package.json +25 -19
  89. package/dist/authoring-references-DYuOAdVx.mjs.map +0 -1
  90. package/dist/capabilities-DKbNCnBf.mjs +0 -66
  91. package/dist/capabilities-DKbNCnBf.mjs.map +0 -1
  92. package/dist/capabilities-DlC1l6qy.d.mts.map +0 -1
  93. package/dist/constants-D-5m_KGB.d.mts.map +0 -1
  94. package/dist/controller-proxy-0WJZUZYx.mjs.map +0 -1
  95. package/dist/controller-proxy-Hkuub9dt.d.mts.map +0 -1
  96. package/dist/engine-0AJrv0hI.mjs.map +0 -1
  97. package/dist/index-BcyfBeDj.d.mts.map +0 -1
  98. package/dist/index-CneD8ibB.d.mts.map +0 -1
  99. package/dist/index-JXH-t2EJ.d.mts.map +0 -1
  100. package/dist/index-MEOL9_Gr.d.mts.map +0 -1
  101. package/dist/memory-thread-repository-1ocPOda8.mjs.map +0 -1
  102. package/dist/protocol-DpCYmCjr.mjs.map +0 -1
  103. package/dist/react-DXK5zWNo.mjs.map +0 -1
  104. package/dist/request-context-BBzU3VdG.mjs.map +0 -1
  105. package/dist/tasks-DcfQNJwO.d.mts.map +0 -1
  106. package/dist/thread-handlers-uddXwQjp.d.mts.map +0 -1
  107. package/dist/tools-wgLen05n.mjs.map +0 -1
package/dist/ui.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
- import { kt as parseSurveyAssistantError } from "./protocol-DpCYmCjr.mjs";
3
- import { f as unwrapSurveyAssistantToolOutput, i as createSurveyAssistantThreadClient, l as parseSurveyChangeSet, p as isSurveyAssistantTurnActive, r as useSurveyAssistantModelCatalog, t as useSurveyAssistantSession } from "./react-DXK5zWNo.mjs";
4
- import { o as connectSurveyAssistantThreadLauncher } from "./controller-proxy-0WJZUZYx.mjs";
2
+ import { jt as parseSurveyAssistantError, l as surveyAssistantTurnFinalSchema, n as SURVEY_ASSISTANT_TURN_FINAL_PART_TYPE } from "./protocol-DATvkr1S.mjs";
3
+ import { f as unwrapSurveyAssistantToolOutput, i as createSurveyAssistantThreadClient, l as parseSurveyChangeSet, p as isSurveyAssistantTurnActive, r as useSurveyAssistantModelCatalog, t as useSurveyAssistantSession } from "./react-Br5YE8H-.mjs";
4
+ import { o as connectSurveyAssistantThreadLauncher } from "./controller-proxy-CpkQRezb.mjs";
5
5
  import { getToolName, isFileUIPart, isReasoningUIPart, isToolUIPart } from "ai";
6
6
  import { createContext, createElement, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
7
7
  import { Collapsible, Select, Slot } from "radix-ui";
@@ -15,6 +15,7 @@ import { createLocalizationTranslator, mergeLocalizationCatalogs } from "@case-f
15
15
  import { cva } from "class-variance-authority";
16
16
  import { StickToBottom, useStickToBottomContext } from "use-stick-to-bottom";
17
17
  import { Streamdown } from "streamdown";
18
+ import { bundledLanguages } from "shiki/langs";
18
19
  //#region src/ui/internal/collapsible.tsx
19
20
  function Collapsible$1({ ...props }) {
20
21
  return /* @__PURE__ */ jsx(Collapsible.Root, {
@@ -114,7 +115,25 @@ const surveyAssistantEnMessages = {
114
115
  startConversation: "Start a conversation to see messages here",
115
116
  scrollToLatest: "Scroll to latest message",
116
117
  working: "Working...",
117
- stopped: "Stopped"
118
+ stopped: "Stopped",
119
+ incomplete: "Response interrupted"
120
+ },
121
+ turn: {
122
+ failedTitle: "The assistant did not complete this turn.",
123
+ abortedTitle: "The turn was stopped.",
124
+ reasons: {
125
+ clientAbort: "The request was stopped by the client.",
126
+ firstResponseTimeout: "No meaningful response arrived before the initial deadline.",
127
+ idleTimeout: "The assistant stopped producing activity.",
128
+ maxDuration: "The one-hour execution ceiling was reached.",
129
+ maxSteps: "The assistant reached its tool-step limit before finishing.",
130
+ lengthLimit: "The model reached its output-length limit.",
131
+ contentFilter: "The provider stopped the response because of its content policy.",
132
+ providerError: "The model provider reported an error.",
133
+ streamError: "The response stream ended with an error.",
134
+ incompleteResponse: "The response ended without a normal completion.",
135
+ unknown: "The response ended before completion."
136
+ }
118
137
  },
119
138
  status: {
120
139
  applying: "Applying changes",
@@ -282,7 +301,25 @@ const surveyAssistantNlMessages = {
282
301
  startConversation: "Start een gesprek om hier berichten te zien",
283
302
  scrollToLatest: "Naar het nieuwste bericht scrollen",
284
303
  working: "Bezig...",
285
- stopped: "Gestopt"
304
+ stopped: "Gestopt",
305
+ incomplete: "Antwoord onderbroken"
306
+ },
307
+ turn: {
308
+ failedTitle: "De assistent heeft deze beurt niet voltooid.",
309
+ abortedTitle: "De beurt is gestopt.",
310
+ reasons: {
311
+ clientAbort: "Het verzoek is door de client gestopt.",
312
+ firstResponseTimeout: "Er kwam geen betekenisvol antwoord vóór de eerste tijdslimiet.",
313
+ idleTimeout: "De assistent produceerde geen activiteit meer.",
314
+ maxDuration: "De maximale uitvoertijd van één uur is bereikt.",
315
+ maxSteps: "De assistent bereikte de limiet voor hulpmiddelstappen voordat hij klaar was.",
316
+ lengthLimit: "Het model bereikte de maximale antwoordlengte.",
317
+ contentFilter: "De provider stopte het antwoord vanwege het inhoudsbeleid.",
318
+ providerError: "De modelprovider meldde een fout.",
319
+ streamError: "De antwoordstream eindigde met een fout.",
320
+ incompleteResponse: "Het antwoord eindigde zonder normale voltooiing.",
321
+ unknown: "Het antwoord eindigde voordat het voltooid was."
322
+ }
286
323
  },
287
324
  status: {
288
325
  applying: "Wijzigingen toepassen",
@@ -671,7 +708,7 @@ const Message = ({ className, from, ...props }) => /* @__PURE__ */ jsx("div", {
671
708
  ...props
672
709
  });
673
710
  const MessageContent = ({ children, className, ...props }) => /* @__PURE__ */ jsx("div", {
674
- className: cn("w-fit min-w-0 max-w-full break-words text-sm", "group-data-[role=user]:whitespace-pre-wrap group-data-[role=user]:rounded-xl group-data-[role=user]:border group-data-[role=user]:border-blue-200/70 group-data-[role=user]:bg-blue-50 group-data-[role=user]:px-3 group-data-[role=user]:py-2 group-data-[role=user]:text-foreground group-data-[role=user]:shadow-xs dark:group-data-[role=user]:border-blue-800/60 dark:group-data-[role=user]:bg-blue-950/50", "group-data-[role=assistant]:text-foreground", className),
711
+ className: cn("w-fit min-w-0 max-w-full break-words text-sm", "group-data-[role=user]:whitespace-pre-wrap group-data-[role=user]:rounded-xl group-data-[role=user]:border group-data-[role=user]:border-blue-200/70 group-data-[role=user]:bg-blue-50/80 group-data-[role=user]:px-3 group-data-[role=user]:py-2 group-data-[role=user]:text-foreground group-data-[role=user]:shadow-xs dark:group-data-[role=user]:border-blue-800/60 dark:group-data-[role=user]:bg-blue-950/50", "group-data-[role=assistant]:text-foreground", className),
675
712
  ...props,
676
713
  children
677
714
  });
@@ -702,13 +739,18 @@ const usePromptInput = () => {
702
739
  if (!context) throw new Error("PromptInput components must be used within PromptInput.");
703
740
  return context;
704
741
  };
705
- const PromptInput = ({ attachmentsDisabled = false, children, className, onDragEnter, onDragLeave, onDragOver, onDrop, onPaste, onSubmit, ...props }) => {
706
- const [value, setValue] = useState("");
742
+ const PromptInput = ({ attachmentsDisabled = false, children, className, onDragEnter, onDragLeave, onDragOver, onDrop, onPaste, onSubmit, onValueChange, value: controlledValue, ...props }) => {
743
+ const [uncontrolledValue, setUncontrolledValue] = useState("");
707
744
  const [files, setFiles] = useState([]);
708
745
  const [isDraggingFiles, setIsDraggingFiles] = useState(false);
709
746
  const [unsupportedFileNames, setUnsupportedFileNames] = useState([]);
710
747
  const fileDragDepthRef = useRef(0);
711
748
  const t = useSurveyAssistantTranslation();
749
+ const value = controlledValue ?? uncontrolledValue;
750
+ const setValue = useCallback((nextValue) => {
751
+ if (controlledValue === void 0) setUncontrolledValue(nextValue);
752
+ onValueChange?.(nextValue);
753
+ }, [controlledValue, onValueChange]);
712
754
  const addFiles = useCallback((fileList) => {
713
755
  const nextFiles = Array.from(fileList ?? []);
714
756
  const supportedFiles = nextFiles.filter(isSupportedFile);
@@ -910,23 +952,6 @@ const PromptInputSubmit = ({ className, status, onStop, onClick, ...props }) =>
910
952
  });
911
953
  };
912
954
  //#endregion
913
- //#region src/ui/internal/tool-technical-details.ts
914
- const formatOmittedLength = (length) => length.toLocaleString("en-US");
915
- const stringifyToolTechnicalDetails = (value) => JSON.stringify(value, function redactEmbeddedBinary(key, currentValue) {
916
- const parent = this && typeof this === "object" ? this : void 0;
917
- if (key === "data" && typeof currentValue === "string" && parent?.type === "embedded" && parent.encoding === "base64") return `[embedded base64 omitted: ${formatOmittedLength(currentValue.length)} characters]`;
918
- if (typeof currentValue === "string" && currentValue.length > 512 && /^data:[^;,]+;base64,/.test(currentValue)) return `[base64 data URL omitted: ${formatOmittedLength(currentValue.length)} characters]`;
919
- return currentValue;
920
- }, 2);
921
- //#endregion
922
- //#region src/ui/internal/response.tsx
923
- const Response = ({ children, className, controls = false, ...props }) => /* @__PURE__ */ jsx(Streamdown, {
924
- className: cn("leading-6", "[&>:first-child]:mt-0 [&>:last-child]:mb-0", "[&_a]:text-primary [&_a]:underline [&_a]:underline-offset-2", "[&_blockquote]:my-3 [&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-3 [&_blockquote]:text-muted-foreground", "[&_code]:rounded-md [&_code]:bg-muted [&_code]:px-1 [&_code]:py-0.5 [&_code]:font-mono [&_code]:text-[0.85em]", "[&_h1]:mb-2 [&_h1]:mt-4 [&_h1]:text-base [&_h1]:font-semibold", "[&_h2]:mb-2 [&_h2]:mt-4 [&_h2]:text-sm [&_h2]:font-semibold", "[&_h3]:mb-1.5 [&_h3]:mt-3 [&_h3]:text-sm [&_h3]:font-semibold", "[&_li]:my-1 [&_ol]:my-2 [&_ol]:list-decimal [&_ol]:pl-5 [&_ul]:my-2 [&_ul]:list-disc [&_ul]:pl-5", "[&_p]:my-2", "[&_pre]:my-3 [&_pre]:overflow-x-auto [&_pre]:rounded-lg [&_pre]:border [&_pre]:bg-muted [&_pre]:p-3", "[&_pre_code]:bg-transparent [&_pre_code]:p-0", "[&_strong]:font-semibold", "[&_table]:my-3 [&_table]:w-full [&_table]:border-collapse [&_td]:border [&_td]:border-border [&_td]:px-2 [&_td]:py-1 [&_th]:border [&_th]:border-border [&_th]:px-2 [&_th]:py-1 [&_th]:text-left [&_th]:font-semibold", className),
925
- controls,
926
- ...props,
927
- children
928
- });
929
- //#endregion
930
955
  //#region src/ui/internal/select.tsx
931
956
  function Select$1({ ...props }) {
932
957
  return /* @__PURE__ */ jsx(Select.Root, {
@@ -1001,6 +1026,88 @@ function SelectScrollDownButton({ className, ...props }) {
1001
1026
  });
1002
1027
  }
1003
1028
  //#endregion
1029
+ //#region src/ui/survey-assistant-prompt-input.tsx
1030
+ const SurveyAssistantModelSelect = ({ disabled = false, modelId, models, onModelChange }) => {
1031
+ const t = useSurveyAssistantTranslation();
1032
+ const selectedModelLabel = models.find((model) => model.id === modelId)?.label ?? modelId ?? t("input.defaultModel");
1033
+ if (models.length > 1) return /* @__PURE__ */ jsxs(Select$1, {
1034
+ disabled,
1035
+ onValueChange: onModelChange,
1036
+ value: modelId ?? "",
1037
+ children: [/* @__PURE__ */ jsxs(SelectTrigger, {
1038
+ "aria-label": t("input.model"),
1039
+ className: "h-8 min-w-0 max-w-48 gap-1.5 border-0 bg-transparent px-2 text-xs shadow-none hover:bg-muted hover:text-foreground focus-visible:ring-1",
1040
+ size: "sm",
1041
+ title: modelId,
1042
+ children: [/* @__PURE__ */ jsx(BotIcon, { className: "size-3.5" }), /* @__PURE__ */ jsx(SelectValue, {})]
1043
+ }), /* @__PURE__ */ jsx(SelectContent, {
1044
+ align: "end",
1045
+ className: "min-w-52 rounded-lg border-border p-0 shadow-lg",
1046
+ position: "popper",
1047
+ children: models.map((model) => /* @__PURE__ */ jsx(SelectItem, {
1048
+ className: "min-h-9 rounded-md px-2 py-2 text-xs focus:bg-muted",
1049
+ title: model.id,
1050
+ value: model.id,
1051
+ children: model.label
1052
+ }, model.id))
1053
+ })]
1054
+ });
1055
+ return /* @__PURE__ */ jsxs("div", {
1056
+ className: "flex h-8 min-w-0 max-w-48 items-center gap-1.5 px-2 text-muted-foreground text-xs",
1057
+ title: modelId,
1058
+ children: [/* @__PURE__ */ jsx(BotIcon, { className: "size-3.5 shrink-0" }), /* @__PURE__ */ jsx("span", {
1059
+ className: "truncate",
1060
+ children: selectedModelLabel
1061
+ })]
1062
+ });
1063
+ };
1064
+ const SurveyAssistantPromptInput = ({ attachmentsDisabled = false, attachmentButtonClassName, className, disabled = false, footerClassName, formProps, modelDisabled = false, modelId, models, onModelChange, onStop, onSubmit, onValueChange, placeholder, status, textareaClassName, textareaProps, textareaRef, value }) => {
1065
+ const t = useSurveyAssistantTranslation();
1066
+ const canStop = Boolean(onStop) && (status === "submitted" || status === "streaming");
1067
+ return /* @__PURE__ */ jsxs(PromptInput, {
1068
+ ...formProps,
1069
+ attachmentsDisabled: attachmentsDisabled || disabled,
1070
+ className: cn("rounded-xl border-border bg-background shadow-sm", className),
1071
+ onSubmit,
1072
+ onValueChange,
1073
+ value,
1074
+ children: [/* @__PURE__ */ jsx(PromptInputBody, { children: /* @__PURE__ */ jsx(PromptInputTextarea, {
1075
+ ...textareaProps,
1076
+ className: cn("min-h-20 px-3 py-2 disabled:cursor-not-allowed disabled:text-muted-foreground", textareaClassName),
1077
+ disabled,
1078
+ placeholder: placeholder ?? t("input.placeholder"),
1079
+ ref: textareaRef
1080
+ }) }), /* @__PURE__ */ jsxs(PromptInputFooter, {
1081
+ className: cn("justify-between border-0 px-1.5 pt-0 pb-1.5", footerClassName),
1082
+ children: [/* @__PURE__ */ jsx(PromptInputAttachmentButton, {
1083
+ className: attachmentButtonClassName,
1084
+ disabled: attachmentsDisabled || disabled
1085
+ }), /* @__PURE__ */ jsxs("div", {
1086
+ className: "flex min-w-0 items-center justify-end gap-1",
1087
+ children: [/* @__PURE__ */ jsx(SurveyAssistantModelSelect, {
1088
+ disabled: disabled || modelDisabled,
1089
+ modelId,
1090
+ models,
1091
+ onModelChange
1092
+ }), /* @__PURE__ */ jsx(PromptInputSubmit, {
1093
+ disabled: disabled && !canStop,
1094
+ onStop,
1095
+ status
1096
+ })]
1097
+ })]
1098
+ })]
1099
+ });
1100
+ };
1101
+ //#endregion
1102
+ //#region src/ui/internal/tool-technical-details.ts
1103
+ const formatOmittedLength = (length) => length.toLocaleString("en-US");
1104
+ const stringifyToolTechnicalDetails = (value) => JSON.stringify(value, function redactEmbeddedBinary(key, currentValue) {
1105
+ const parent = this && typeof this === "object" ? this : void 0;
1106
+ if (key === "data" && typeof currentValue === "string" && parent?.type === "embedded" && parent.encoding === "base64") return `[embedded base64 omitted: ${formatOmittedLength(currentValue.length)} characters]`;
1107
+ if (typeof currentValue === "string" && currentValue.length > 512 && /^data:[^;,]+;base64,/.test(currentValue)) return `[base64 data URL omitted: ${formatOmittedLength(currentValue.length)} characters]`;
1108
+ return currentValue;
1109
+ }, 2);
1110
+ //#endregion
1004
1111
  //#region src/ui/internal/code-block.tsx
1005
1112
  const isItalic = (fontStyle) => fontStyle && fontStyle & 1;
1006
1113
  const isBold = (fontStyle) => fontStyle && fontStyle & 2;
@@ -1172,6 +1279,30 @@ const CodeBlock = ({ code, language, showLineNumbers = false, className, childre
1172
1279
  });
1173
1280
  };
1174
1281
  //#endregion
1282
+ //#region src/ui/internal/response.tsx
1283
+ const highlightedLanguages = Object.keys(bundledLanguages);
1284
+ const codeHighlighter = {
1285
+ getSupportedLanguages: () => highlightedLanguages,
1286
+ getThemes: () => ["github-light", "github-dark"],
1287
+ highlight: ({ code, language }, callback) => highlightCode(code, language, callback),
1288
+ name: "shiki",
1289
+ supportsLanguage: (language) => language in bundledLanguages,
1290
+ type: "code-highlighter"
1291
+ };
1292
+ const Response = ({ children, className, controls = false, plugins, ...props }) => {
1293
+ const responsePlugins = useMemo(() => ({
1294
+ ...plugins,
1295
+ code: plugins?.code ?? codeHighlighter
1296
+ }), [plugins]);
1297
+ return /* @__PURE__ */ jsx(Streamdown, {
1298
+ className: cn("leading-6", "*:first:mt-0 *:last:mb-0", "[&_a]:text-primary [&_a]:underline [&_a]:underline-offset-2", "[&_blockquote]:my-3 [&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-3 [&_blockquote]:text-muted-foreground", "[&_h1]:mb-2 [&_h1]:mt-4 [&_h1]:text-base [&_h1]:font-semibold", "[&_h2]:mb-2 [&_h2]:mt-4 [&_h2]:text-sm [&_h2]:font-semibold", "[&_h3]:mb-1.5 [&_h3]:mt-3 [&_h3]:text-sm [&_h3]:font-semibold", "[&_li]:my-1 [&_ol]:my-2 [&_ol]:list-decimal [&_ol]:pl-5 [&_ul]:my-2 [&_ul]:list-disc [&_ul]:pl-5", "[&_p]:my-2", "**:data-[streamdown=code-block]:my-3 **:data-[streamdown=code-block]:gap-0 **:data-[streamdown=code-block]:overflow-hidden **:data-[streamdown=code-block]:rounded-lg **:data-[streamdown=code-block]:bg-muted/40 **:data-[streamdown=code-block]:p-0", "**:data-[streamdown=code-block-header]:px-2", "**:data-[streamdown=code-block-body]:rounded-none **:data-[streamdown=code-block-body]:border-0 **:data-[streamdown=code-block-body]:border-t **:data-[streamdown=code-block-body]:bg-background **:data-[streamdown=code-block-body]:p-3", "[&_strong]:font-semibold", "[&_table]:my-3 [&_table]:w-full [&_table]:border-collapse [&_td]:border [&_td]:border-border [&_td]:px-2 [&_td]:py-1 [&_th]:border [&_th]:border-border [&_th]:px-2 [&_th]:py-1 [&_th]:text-left [&_th]:font-semibold", className),
1299
+ controls,
1300
+ plugins: responsePlugins,
1301
+ ...props,
1302
+ children
1303
+ });
1304
+ };
1305
+ //#endregion
1175
1306
  //#region src/ui/internal/message-timing.ts
1176
1307
  const isRecord$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1177
1308
  const asTimestamp = (value) => {
@@ -1198,6 +1329,9 @@ const getPartTimestamps = (part) => {
1198
1329
  * time here keeps restored chats informative without a second timing store.
1199
1330
  */
1200
1331
  const getPersistedMessageDurationSeconds = (message) => {
1332
+ const turnFinalPart = message.parts.find((part) => part.type === SURVEY_ASSISTANT_TURN_FINAL_PART_TYPE);
1333
+ const turnFinal = surveyAssistantTurnFinalSchema.safeParse(turnFinalPart && "data" in turnFinalPart ? turnFinalPart.data : void 0);
1334
+ if (turnFinal.success && turnFinal.data.durationMs !== void 0) return Math.max(1, Math.ceil(turnFinal.data.durationMs / 1e3));
1201
1335
  if (!isRecord$1(message.metadata)) return void 0;
1202
1336
  const startedAt = asTimestamp(message.metadata.createdAt);
1203
1337
  if (startedAt === void 0) return void 0;
@@ -1206,6 +1340,16 @@ const getPersistedMessageDurationSeconds = (message) => {
1206
1340
  return Math.max(1, Math.ceil((completedAt - startedAt) / 1e3));
1207
1341
  };
1208
1342
  //#endregion
1343
+ //#region src/ui/internal/message-turn-status.ts
1344
+ const getPersistedMessageTurnStatus = (message) => {
1345
+ for (let index = message.parts.length - 1; index >= 0; index -= 1) {
1346
+ const part = message.parts[index];
1347
+ if (part?.type !== SURVEY_ASSISTANT_TURN_FINAL_PART_TYPE || !("data" in part)) continue;
1348
+ const parsed = surveyAssistantTurnFinalSchema.safeParse(part.data);
1349
+ if (parsed.success) return parsed.data.status;
1350
+ }
1351
+ };
1352
+ //#endregion
1209
1353
  //#region src/ui/survey-assistant-panel.tsx
1210
1354
  const getAssistantQuickPrompts = (t) => [
1211
1355
  {
@@ -1283,7 +1427,7 @@ const SurveyAssistantToolOutputSummary = ({ applyResult, output }) => {
1283
1427
  const statusLabel = status === "applied" ? t("changes.applied") : status === "stale" ? t("changes.stale") : status === "failed" ? t("changes.failed") : isValid ? t("changes.prepared") : t("changes.rejected");
1284
1428
  const statusMessage = applyResult?.message ?? unwrappedOutput.summary;
1285
1429
  return /* @__PURE__ */ jsxs("div", {
1286
- className: cn("relative rounded-md border px-2.5 py-2 text-xs", isPositiveStatus ? "border-emerald-200/80 bg-emerald-50/60 text-emerald-950" : status === "stale" ? "border-amber-200 bg-amber-50 text-amber-900" : "border-red-200 bg-red-50 text-red-900"),
1430
+ className: cn("relative rounded-md border px-2.5 py-2 text-xs", isPositiveStatus ? "border-emerald-200/70 bg-emerald-50/40 text-emerald-950" : status === "stale" ? "border-amber-200/70 bg-amber-50/40 text-amber-900" : "border-red-200/70 bg-red-50/40 text-red-900"),
1287
1431
  children: [
1288
1432
  /* @__PURE__ */ jsxs("div", {
1289
1433
  className: "flex items-center gap-2 font-medium",
@@ -1373,7 +1517,7 @@ const getStoppedToolStatusMeta = (t) => ({
1373
1517
  label: t("activity.stopped"),
1374
1518
  stepStatus: "complete"
1375
1519
  });
1376
- const SurveyAssistantToolStep = ({ applyResultsByChangeId, isAborted, part, isLast }) => {
1520
+ const SurveyAssistantToolStep = ({ applyResultsByChangeId, terminalTurnStatus, part, isLast }) => {
1377
1521
  const t = useSurveyAssistantTranslation();
1378
1522
  const toolStatusMeta = getToolStatusMeta(t);
1379
1523
  const toolName = getToolName(part);
@@ -1381,8 +1525,21 @@ const SurveyAssistantToolStep = ({ applyResultsByChangeId, isAborted, part, isLa
1381
1525
  const errorText = getToolErrorText(part);
1382
1526
  const changeSet = output === void 0 ? null : getChangeSet(output);
1383
1527
  const applyResult = changeSet ? applyResultsByChangeId[changeSet.id] : void 0;
1528
+ const changeHasError = changeSet?.status === "rejected" || applyResult?.status === "rejected" || applyResult?.status === "failed";
1384
1529
  const title = changeSet ? toolName === "survey_expression" ? t("activity.expressionChange") : t("activity.surveyChange") : getToolTitle(t, toolName, part.state);
1385
- const meta = isAborted && toolStatusMeta[part.state].stepStatus === "active" ? getStoppedToolStatusMeta(t) : toolStatusMeta[part.state];
1530
+ const meta = terminalTurnStatus && toolStatusMeta[part.state].stepStatus === "active" ? terminalTurnStatus === "aborted" ? getStoppedToolStatusMeta(t) : {
1531
+ icon: /* @__PURE__ */ jsx(XCircleIcon, { className: "size-3 text-red-600" }),
1532
+ label: t("activity.failed"),
1533
+ stepStatus: "complete"
1534
+ } : changeHasError ? {
1535
+ icon: /* @__PURE__ */ jsx(XCircleIcon, { className: "size-3 text-red-900" }),
1536
+ label: null,
1537
+ stepStatus: "complete"
1538
+ } : applyResult?.status === "stale" ? {
1539
+ icon: /* @__PURE__ */ jsx(ClockIcon, { className: "size-3 text-amber-600" }),
1540
+ label: null,
1541
+ stepStatus: "complete"
1542
+ } : toolStatusMeta[part.state];
1386
1543
  const metaTitle = meta.label ?? (part.state === "output-denied" ? t("activity.skipped") : part.state === "output-error" ? t("activity.failed") : meta.stepStatus === "active" ? t("activity.workingShort") : t("activity.done"));
1387
1544
  const [detailsOpen, setDetailsOpen] = useState(part.state === "output-error");
1388
1545
  const hasDetails = part.input !== void 0 || output !== void 0 || Boolean(errorText);
@@ -1413,16 +1570,16 @@ const SurveyAssistantToolStep = ({ applyResultsByChangeId, isAborted, part, isLa
1413
1570
  onOpenChange: setDetailsOpen,
1414
1571
  open: detailsOpen,
1415
1572
  children: [/* @__PURE__ */ jsxs(CollapsibleTrigger, {
1416
- className: "flex items-center gap-1 text-muted-foreground text-xs hover:text-foreground",
1573
+ className: "-ml-1 flex items-center gap-0.5 rounded px-1 py-0.5 text-[11px] text-muted-foreground/80 transition-colors hover:bg-background/60 hover:text-foreground",
1417
1574
  children: [/* @__PURE__ */ jsx(ChevronRightIcon, { className: cn("size-3 transition-transform", detailsOpen && "rotate-90") }), t(detailsOpen ? "activity.hideTechnicalDetails" : "activity.technicalDetails")]
1418
1575
  }), /* @__PURE__ */ jsxs(CollapsibleContent, {
1419
- className: "mt-2 space-y-2",
1576
+ className: "mt-1.5 space-y-1.5",
1420
1577
  children: [part.input !== void 0 && /* @__PURE__ */ jsx(CodeBlock, {
1421
- className: "border-border/50 bg-muted/30",
1578
+ className: "border-border/60 bg-background/70 [&_code]:text-xs [&_pre]:p-2.5 [&_pre]:text-xs",
1422
1579
  code: stringifyToolTechnicalDetails(part.input),
1423
1580
  language: "json"
1424
1581
  }), (output !== void 0 || errorText) && /* @__PURE__ */ jsx(CodeBlock, {
1425
- className: cn("bg-muted/30", errorText ? "border-destructive/30" : "border-border/50"),
1582
+ className: cn("bg-background/70 [&_code]:text-xs [&_pre]:p-2.5 [&_pre]:text-xs", errorText ? "border-destructive/25" : "border-border/60"),
1426
1583
  code: errorText ?? stringifyToolTechnicalDetails(output),
1427
1584
  language: "json"
1428
1585
  })]
@@ -1508,7 +1665,7 @@ const buildMessageBlocks = (parts) => {
1508
1665
  flushChain();
1509
1666
  return blocks;
1510
1667
  };
1511
- const SurveyAssistantMessageParts = ({ applyResultsByChangeId, isAborted, isLastMessage, isStreaming, message, showProgress }) => {
1668
+ const SurveyAssistantMessageParts = ({ applyResultsByChangeId, isLastMessage, isStreaming, message, showProgress, terminalTurnStatus }) => {
1512
1669
  const blocks = useMemo(() => buildMessageBlocks(message.parts), [message.parts]);
1513
1670
  const persistedDuration = useMemo(() => getPersistedMessageDurationSeconds(message), [message]);
1514
1671
  const lastPart = message.parts.at(-1);
@@ -1522,11 +1679,9 @@ const SurveyAssistantMessageParts = ({ applyResultsByChangeId, isAborted, isLast
1522
1679
  }, block.key);
1523
1680
  return /* @__PURE__ */ jsx("span", { children: block.text }, block.key);
1524
1681
  }
1525
- if (block.type === "file") return /* @__PURE__ */ jsxs("a", {
1526
- className: "inline-flex max-w-full items-center gap-2 rounded-md border border-border bg-muted px-2 py-1 text-xs hover:bg-muted/80",
1527
- href: block.part.url,
1528
- rel: "noreferrer",
1529
- target: "_blank",
1682
+ if (block.type === "file") return /* @__PURE__ */ jsxs("span", {
1683
+ className: "inline-flex max-w-full items-center gap-2 rounded-md border border-border bg-muted px-2 py-1 text-xs",
1684
+ title: getFilePartLabel(block.part),
1530
1685
  children: [
1531
1686
  /* @__PURE__ */ jsx(FileIcon, { className: "size-3.5 shrink-0 text-muted-foreground" }),
1532
1687
  /* @__PURE__ */ jsx("span", {
@@ -1557,9 +1712,10 @@ const SurveyAssistantMessageParts = ({ applyResultsByChangeId, isAborted, isLast
1557
1712
  const isChainActive = isLastMessage && isStreaming && isLastBlock;
1558
1713
  return /* @__PURE__ */ jsxs(ChainOfThought, {
1559
1714
  autoClose: true,
1560
- className: "w-full rounded-md bg-muted/35 px-2 py-1.5",
1715
+ className: "w-full rounded-md bg-muted/60 px-2 py-1.5",
1561
1716
  defaultDuration: persistedDuration,
1562
1717
  defaultOpen: isChainActive,
1718
+ duration: isChainActive ? void 0 : persistedDuration,
1563
1719
  isStreaming: isChainActive,
1564
1720
  children: [/* @__PURE__ */ jsx(ChainOfThoughtHeader, { className: "text-xs font-medium" }), /* @__PURE__ */ jsxs(ChainOfThoughtContent, {
1565
1721
  className: "relative mt-2",
@@ -1574,9 +1730,9 @@ const SurveyAssistantMessageParts = ({ applyResultsByChangeId, isAborted, isLast
1574
1730
  }, entry.key);
1575
1731
  return /* @__PURE__ */ jsx(SurveyAssistantToolStep, {
1576
1732
  applyResultsByChangeId,
1577
- isAborted,
1578
1733
  isLast: isLastEntry,
1579
- part: entry.part
1734
+ part: entry.part,
1735
+ terminalTurnStatus
1580
1736
  }, entry.key);
1581
1737
  })]
1582
1738
  })]
@@ -1585,6 +1741,33 @@ const SurveyAssistantMessageParts = ({ applyResultsByChangeId, isAborted, isLast
1585
1741
  };
1586
1742
  const SurveyAssistantTurnOutcomeNotice = (outcome) => {
1587
1743
  const t = useSurveyAssistantTranslation();
1744
+ if (outcome.status === "failed" || outcome.status === "aborted") {
1745
+ const reason = (() => {
1746
+ switch (outcome.terminationReason) {
1747
+ case "client-abort": return t("turn.reasons.clientAbort");
1748
+ case "first-response-timeout": return t("turn.reasons.firstResponseTimeout");
1749
+ case "idle-timeout": return t("turn.reasons.idleTimeout");
1750
+ case "max-duration": return t("turn.reasons.maxDuration");
1751
+ case "max-steps": return t("turn.reasons.maxSteps");
1752
+ case "length-limit": return t("turn.reasons.lengthLimit");
1753
+ case "content-filter": return t("turn.reasons.contentFilter");
1754
+ case "provider-error": return t("turn.reasons.providerError");
1755
+ case "stream-error": return t("turn.reasons.streamError");
1756
+ case "incomplete-response": return t("turn.reasons.incompleteResponse");
1757
+ default: return t("turn.reasons.unknown");
1758
+ }
1759
+ })();
1760
+ return /* @__PURE__ */ jsxs("div", {
1761
+ className: cn("rounded-lg border px-3 py-2 text-xs", outcome.status === "aborted" ? "border-border bg-muted/50 text-foreground" : "border-destructive/20 bg-destructive/5 text-foreground"),
1762
+ children: [/* @__PURE__ */ jsx("div", {
1763
+ className: "font-medium",
1764
+ children: t(outcome.status === "aborted" ? "turn.abortedTitle" : "turn.failedTitle")
1765
+ }), /* @__PURE__ */ jsx("div", {
1766
+ className: "mt-1 text-muted-foreground",
1767
+ children: reason
1768
+ })]
1769
+ });
1770
+ }
1588
1771
  const description = outcome.status === "blocked" ? t(outcome.blockedChangeSetCount === 1 ? "changes.blocked" : "changes.blockedPlural", { count: outcome.blockedChangeSetCount }) : t(outcome.appliedChangeSetCount === 1 ? "changes.partial" : "changes.partialPlural", {
1589
1772
  applied: outcome.appliedChangeSetCount,
1590
1773
  failed: outcome.failedChangeSetCount
@@ -1642,9 +1825,9 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1642
1825
  surveyId,
1643
1826
  threadId
1644
1827
  });
1828
+ const hasSubmittedMessageRef = useRef((initialMessages?.length ?? 0) > 0);
1645
1829
  const showAssistantError = Boolean(authorizationError) || Boolean(error);
1646
1830
  const assistantErrorPresentation = getAssistantErrorPresentation(authorizationError ?? error, t);
1647
- const selectedModelLabel = models.find((model) => model.id === modelId)?.label ?? modelId ?? t("input.defaultModel");
1648
1831
  const lastMessage = messages.at(-1);
1649
1832
  const isAssistantTurnActive = isSurveyAssistantTurnActive({
1650
1833
  isStreaming,
@@ -1691,7 +1874,7 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1691
1874
  onTurnSettled
1692
1875
  ]);
1693
1876
  const showPendingAssistantRow = isAssistantTurnActive && (!lastMessage || lastMessage.role !== "assistant");
1694
- const sessionStatusLabel = state === "applying" ? t("status.applying") : state === "applied" ? t("status.applied") : state === "aborted" ? t("status.stopped") : isAssistantTurnActive ? getStreamingProgressLabel(t, lastMessage) : showAssistantError ? t("status.attention") : t("status.ready");
1877
+ const sessionStatusLabel = state === "applying" ? t("status.applying") : state === "applied" ? t("status.applied") : state === "aborted" ? t("status.stopped") : isAssistantTurnActive ? getStreamingProgressLabel(t, lastMessage) : showAssistantError || state === "failed" ? t("status.attention") : t("status.ready");
1695
1878
  const handleSubmit = useCallback((message) => {
1696
1879
  if (isAssistantTurnActive) return false;
1697
1880
  const submittedFromFocusedPrompt = document.activeElement === promptTextareaRef.current;
@@ -1701,7 +1884,10 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1701
1884
  });
1702
1885
  if (accepted) {
1703
1886
  shouldRestorePromptFocusRef.current = submittedFromFocusedPrompt;
1704
- onFirstSubmit(message.text);
1887
+ if (!hasSubmittedMessageRef.current) {
1888
+ hasSubmittedMessageRef.current = true;
1889
+ onFirstSubmit(message.text);
1890
+ }
1705
1891
  }
1706
1892
  return accepted;
1707
1893
  }, [
@@ -1715,18 +1901,24 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1715
1901
  }), [handleSubmit]);
1716
1902
  useEffect(() => {
1717
1903
  if (!initialLaunch) return;
1904
+ if (initialLaunch.modelId) onModelChange(initialLaunch.modelId);
1718
1905
  if (submit({
1719
1906
  files: initialLaunch.files ?? [],
1907
+ modelId: initialLaunch.modelId,
1720
1908
  text: initialLaunch.prompt,
1721
1909
  uiContext: initialLaunch.uiContext
1722
1910
  })) {
1723
- onFirstSubmit(initialLaunch.prompt);
1911
+ if (!hasSubmittedMessageRef.current) {
1912
+ hasSubmittedMessageRef.current = true;
1913
+ onFirstSubmit(initialLaunch.prompt);
1914
+ }
1724
1915
  onInitialLaunchHandled();
1725
1916
  }
1726
1917
  }, [
1727
1918
  initialLaunch,
1728
1919
  onFirstSubmit,
1729
1920
  onInitialLaunchHandled,
1921
+ onModelChange,
1730
1922
  submit
1731
1923
  ]);
1732
1924
  return /* @__PURE__ */ jsxs("aside", {
@@ -1734,15 +1926,15 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1734
1926
  className: cn("flex h-full min-h-0 flex-col overflow-visible bg-background", !embedded && "rounded-xl border border-border shadow-sm"),
1735
1927
  children: [
1736
1928
  /* @__PURE__ */ jsxs("header", {
1737
- className: "box-content flex h-9 shrink-0 items-center justify-between gap-2 border-b border-border bg-muted/20 px-2.5",
1929
+ className: "box-content flex h-9 shrink-0 items-center justify-between gap-2 border-b border-border bg-background px-2.5",
1738
1930
  children: [/* @__PURE__ */ jsx(SurveyAssistantHeaderIdentity, {
1739
- indicatorClassName: showAssistantError ? "bg-destructive" : isAssistantTurnActive ? "animate-pulse bg-blue-500" : state === "aborted" ? "bg-muted-foreground" : "bg-emerald-500",
1931
+ indicatorClassName: showAssistantError || state === "failed" ? "bg-destructive" : isAssistantTurnActive ? "animate-pulse bg-blue-500" : state === "aborted" ? "bg-muted-foreground" : "bg-emerald-500",
1740
1932
  status: sessionStatusLabel,
1741
1933
  title: threadTitle
1742
1934
  }), threadControls]
1743
1935
  }),
1744
1936
  /* @__PURE__ */ jsxs(Conversation, {
1745
- className: "min-h-0",
1937
+ className: "min-h-0 bg-(--surface-bg-color)",
1746
1938
  children: [/* @__PURE__ */ jsx(ConversationContent, {
1747
1939
  className: "gap-3 px-3 py-2.5",
1748
1940
  children: messages.length === 0 ? /* @__PURE__ */ jsx(ConversationEmptyState, {
@@ -1765,7 +1957,7 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1765
1957
  /* @__PURE__ */ jsx("div", {
1766
1958
  className: "mt-4 flex flex-col gap-1.5",
1767
1959
  children: assistantQuickPrompts.map(({ label, description, icon: Icon, prompt }) => /* @__PURE__ */ jsxs("button", {
1768
- className: "group flex w-full items-center gap-2.5 rounded-lg border border-border bg-background px-2.5 py-2 text-left transition-colors hover:border-foreground/20 hover:bg-muted/50",
1960
+ className: "group flex w-full items-center gap-2.5 rounded-lg border border-border bg-popover px-2.5 py-2 text-left shadow-xs transition-colors hover:border-foreground/20 hover:bg-muted/60",
1769
1961
  onClick: () => handleQuickPrompt(prompt),
1770
1962
  type: "button",
1771
1963
  children: [
@@ -1791,6 +1983,8 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1791
1983
  })
1792
1984
  }) : /* @__PURE__ */ jsxs(Fragment, { children: [messages.map((message, index) => {
1793
1985
  const isLastMessage = index === messages.length - 1;
1986
+ const persistedTurnStatus = getPersistedMessageTurnStatus(message);
1987
+ const terminalTurnStatus = persistedTurnStatus === "aborted" || persistedTurnStatus === "failed" ? persistedTurnStatus : isLastMessage && (state === "aborted" || state === "failed") ? state : void 0;
1794
1988
  const shouldShowProgress = isAssistantTurnActive && isLastMessage && message.role === "assistant";
1795
1989
  return /* @__PURE__ */ jsx(Message, {
1796
1990
  className: "max-w-[96%] gap-1",
@@ -1799,17 +1993,17 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1799
1993
  className: cn(message.role === "assistant" && "w-full space-y-2"),
1800
1994
  children: hasRenderableParts(message) ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(SurveyAssistantMessageParts, {
1801
1995
  applyResultsByChangeId,
1802
- isAborted: state === "aborted",
1803
1996
  isLastMessage,
1804
1997
  isStreaming: isAssistantTurnActive,
1805
1998
  message,
1806
- showProgress: shouldShowProgress
1807
- }), isLastMessage && latestTurnOutcome && /* @__PURE__ */ jsx(SurveyAssistantTurnOutcomeNotice, { ...latestTurnOutcome })] }) : shouldShowProgress ? /* @__PURE__ */ jsx(SurveyAssistantProgressIndicator, { message }) : state === "aborted" ? /* @__PURE__ */ jsx("span", {
1999
+ showProgress: shouldShowProgress,
2000
+ terminalTurnStatus
2001
+ }), isLastMessage && latestTurnOutcome && /* @__PURE__ */ jsx(SurveyAssistantTurnOutcomeNotice, { ...latestTurnOutcome })] }) : shouldShowProgress ? /* @__PURE__ */ jsx(SurveyAssistantProgressIndicator, { message }) : isLastMessage && latestTurnOutcome ? /* @__PURE__ */ jsx(SurveyAssistantTurnOutcomeNotice, { ...latestTurnOutcome }) : state === "aborted" ? /* @__PURE__ */ jsx("span", {
1808
2002
  className: "text-muted-foreground text-sm",
1809
2003
  children: t("conversation.stopped")
1810
2004
  }) : /* @__PURE__ */ jsx("span", {
1811
2005
  className: "text-muted-foreground text-sm",
1812
- children: t("conversation.working")
2006
+ children: t("conversation.incomplete")
1813
2007
  })
1814
2008
  })
1815
2009
  }, message.id);
@@ -1860,58 +2054,21 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1860
2054
  })]
1861
2055
  }),
1862
2056
  /* @__PURE__ */ jsx("div", {
1863
- className: "shrink-0 border-t border-border bg-muted/20 p-2",
1864
- children: /* @__PURE__ */ jsxs(PromptInput, {
1865
- "aria-busy": isAssistantTurnActive,
1866
- attachmentsDisabled: isAssistantTurnActive,
1867
- className: cn("rounded-xl border-border bg-background shadow-sm transition-colors", isAssistantTurnActive && "border-border/60 bg-muted/50 shadow-none"),
2057
+ className: "shrink-0 border-t border-border bg-background p-2",
2058
+ children: /* @__PURE__ */ jsx(SurveyAssistantPromptInput, {
2059
+ attachmentButtonClassName: "size-8",
2060
+ className: cn("rounded-xl border-border bg-popover shadow-sm transition-colors", isAssistantTurnActive && "border-border/60 bg-muted/50 shadow-none"),
2061
+ disabled: isAssistantTurnActive,
2062
+ footerClassName: cn("transition-colors", isAssistantTurnActive && "bg-muted/30"),
2063
+ formProps: { "aria-busy": isAssistantTurnActive },
2064
+ modelId,
2065
+ models,
2066
+ onModelChange,
2067
+ onStop: stop,
1868
2068
  onSubmit: handleSubmit,
1869
- children: [/* @__PURE__ */ jsx(PromptInputBody, { children: /* @__PURE__ */ jsx(PromptInputTextarea, {
1870
- className: "min-h-11 max-h-28 px-3 pt-2.5 pb-1.5 transition-colors disabled:cursor-not-allowed disabled:text-muted-foreground disabled:placeholder:text-muted-foreground/60",
1871
- disabled: isAssistantTurnActive,
1872
- placeholder: t("input.placeholder"),
1873
- ref: promptTextareaRef
1874
- }) }), /* @__PURE__ */ jsxs(PromptInputFooter, {
1875
- className: cn("justify-between border-0 px-1.5 pt-0 pb-1.5 transition-colors", isAssistantTurnActive && "bg-muted/30"),
1876
- children: [/* @__PURE__ */ jsx(PromptInputAttachmentButton, {
1877
- className: "size-8",
1878
- disabled: isAssistantTurnActive
1879
- }), /* @__PURE__ */ jsxs("div", {
1880
- className: "flex min-w-0 items-center justify-end gap-1",
1881
- children: [models.length > 1 && modelId ? /* @__PURE__ */ jsxs(Select$1, {
1882
- disabled: isAssistantTurnActive,
1883
- onValueChange: onModelChange,
1884
- value: modelId,
1885
- children: [/* @__PURE__ */ jsxs(SelectTrigger, {
1886
- "aria-label": t("input.model"),
1887
- className: "h-8 min-w-0 max-w-48 gap-1.5 border-0 bg-transparent px-2 text-xs shadow-none hover:bg-muted hover:text-foreground focus-visible:ring-1",
1888
- size: "sm",
1889
- title: modelId,
1890
- children: [/* @__PURE__ */ jsx(BotIcon, { className: "size-3.5" }), /* @__PURE__ */ jsx(SelectValue, {})]
1891
- }), /* @__PURE__ */ jsx(SelectContent, {
1892
- align: "end",
1893
- className: "min-w-52 rounded-lg border-border p-0 shadow-lg",
1894
- position: "popper",
1895
- children: models.map((model) => /* @__PURE__ */ jsx(SelectItem, {
1896
- className: "min-h-9 rounded-md px-2 py-2 text-xs focus:bg-muted",
1897
- title: model.id,
1898
- value: model.id,
1899
- children: model.label
1900
- }, model.id))
1901
- })]
1902
- }) : /* @__PURE__ */ jsxs("div", {
1903
- className: "flex h-8 min-w-0 max-w-48 items-center gap-1.5 px-2 text-muted-foreground text-xs",
1904
- title: modelId,
1905
- children: [/* @__PURE__ */ jsx(BotIcon, { className: "size-3.5 shrink-0" }), /* @__PURE__ */ jsx("span", {
1906
- className: "truncate",
1907
- children: selectedModelLabel
1908
- })]
1909
- }), /* @__PURE__ */ jsx(PromptInputSubmit, {
1910
- onStop: stop,
1911
- status
1912
- })]
1913
- })]
1914
- })]
2069
+ status,
2070
+ textareaClassName: "min-h-11 max-h-28 px-3 pt-2.5 pb-1.5 transition-colors disabled:placeholder:text-muted-foreground/60",
2071
+ textareaRef: promptTextareaRef
1915
2072
  })
1916
2073
  })
1917
2074
  ]
@@ -1924,7 +2081,7 @@ const createThreadTitle = (text, fallback) => {
1924
2081
  const words = normalized.split(" ").slice(0, 8).join(" ");
1925
2082
  return words.length > 64 ? `${words.slice(0, 61).trimEnd()}...` : words;
1926
2083
  };
1927
- const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], onConfirmDeleteThread, onModelChange = () => {}, resourceId, selectedThreadStorageKey, surveyId, threadApi, threadClient: providedThreadClient, threadLauncher, ...sessionProps }) => {
2084
+ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], onConfirmDeleteThread, onModelChange = () => {}, onTurnActiveChange: onAssistantTurnActiveChange, resourceId, selectedThreadStorageKey, surveyId, threadApi, threadClient: providedThreadClient, threadLauncher, ...sessionProps }) => {
1928
2085
  const t = useSurveyAssistantTranslation();
1929
2086
  const threadClient = useMemo(() => providedThreadClient ?? createSurveyAssistantThreadClient(threadApi), [providedThreadClient, threadApi]);
1930
2087
  const storageKey = selectedThreadStorageKey === false ? null : selectedThreadStorageKey ?? `${SELECTED_THREAD_STORAGE_KEY}:${encodeURIComponent(resourceId)}${surveyId ? `:${encodeURIComponent(surveyId)}` : ""}`;
@@ -1936,6 +2093,7 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
1936
2093
  const [editingTitle, setEditingTitle] = useState("");
1937
2094
  const [threadError, setThreadError] = useState(null);
1938
2095
  const [loadingThread, setLoadingThread] = useState(true);
2096
+ const [assistantTurnActive, setAssistantTurnActive] = useState(false);
1939
2097
  const [initialLaunch, setInitialLaunch] = useState();
1940
2098
  const historyControlsRef = useRef(null);
1941
2099
  const selectModel = useCallback((modelId) => {
@@ -2062,6 +2220,14 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2062
2220
  setEditingThreadId(null);
2063
2221
  setEditingTitle("");
2064
2222
  }, []);
2223
+ const handleTurnActiveChange = useCallback((active) => {
2224
+ setAssistantTurnActive(active);
2225
+ onAssistantTurnActiveChange?.(active);
2226
+ if (active) {
2227
+ cancelEditingThread();
2228
+ setHistoryOpen(false);
2229
+ }
2230
+ }, [cancelEditingThread, onAssistantTurnActiveChange]);
2065
2231
  const commitEditingThread = useCallback(async () => {
2066
2232
  if (!editingThreadId) return;
2067
2233
  const thread = threads.find((item) => item.id === editingThreadId);
@@ -2094,10 +2260,9 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2094
2260
  threads
2095
2261
  ]);
2096
2262
  const handleFirstSubmit = useCallback((text) => {
2097
- if (!selectedThread || initialMessages.length > 0) return;
2263
+ if (!selectedThread || selectedThread.titleMode === "manual") return;
2098
2264
  setAutomaticTitle(selectedThread, createThreadTitle(text, t("threads.newChat")));
2099
2265
  }, [
2100
- initialMessages.length,
2101
2266
  selectedThread,
2102
2267
  setAutomaticTitle,
2103
2268
  t
@@ -2176,7 +2341,7 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2176
2341
  if (loadingThread || !selectedThread) return /* @__PURE__ */ jsxs("aside", {
2177
2342
  className: cn("flex h-full min-h-0 flex-col overflow-visible bg-background", !embedded && "rounded-xl border border-border shadow-sm"),
2178
2343
  children: [/* @__PURE__ */ jsx("header", {
2179
- className: "box-content flex h-9 shrink-0 items-center border-b border-border bg-muted/20 px-2.5",
2344
+ className: "box-content flex h-9 shrink-0 items-center border-b border-border bg-background px-2.5",
2180
2345
  children: /* @__PURE__ */ jsx(SurveyAssistantHeaderIdentity, {
2181
2346
  indicatorClassName: "animate-pulse bg-blue-500",
2182
2347
  status: t("panel.loadingChats"),
@@ -2193,7 +2358,8 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2193
2358
  children: [
2194
2359
  /* @__PURE__ */ jsx("button", {
2195
2360
  "aria-label": t("threads.newChat"),
2196
- className: "flex size-8 items-center justify-center rounded-lg text-muted-foreground hover:bg-muted hover:text-foreground",
2361
+ className: "flex size-8 items-center justify-center rounded-lg text-muted-foreground hover:bg-muted hover:text-foreground disabled:pointer-events-none disabled:opacity-50",
2362
+ disabled: assistantTurnActive,
2197
2363
  onClick: () => void createThread(),
2198
2364
  title: t("threads.newChat"),
2199
2365
  type: "button",
@@ -2202,7 +2368,8 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2202
2368
  /* @__PURE__ */ jsx("button", {
2203
2369
  "aria-expanded": historyOpen,
2204
2370
  "aria-label": t("threads.history"),
2205
- className: "flex size-8 items-center justify-center rounded-lg text-muted-foreground hover:bg-muted hover:text-foreground",
2371
+ className: "flex size-8 items-center justify-center rounded-lg text-muted-foreground hover:bg-muted hover:text-foreground disabled:pointer-events-none disabled:opacity-50",
2372
+ disabled: assistantTurnActive,
2206
2373
  onClick: toggleHistory,
2207
2374
  title: t("threads.history"),
2208
2375
  type: "button",
@@ -2286,13 +2453,14 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2286
2453
  onModelChange: selectModel,
2287
2454
  onFirstSubmit: handleFirstSubmit,
2288
2455
  onTurnSettled: handleTurnSettled,
2456
+ onTurnActiveChange: handleTurnActiveChange,
2289
2457
  resourceId,
2290
2458
  surveyId,
2291
2459
  threadControls,
2292
2460
  threadId: selectedThread.id,
2293
2461
  threadTitle: selectedThread.title
2294
2462
  }), threadError && /* @__PURE__ */ jsxs("div", {
2295
- className: "absolute right-3 bottom-3 left-3 z-40 rounded-lg border border-destructive/20 bg-background px-3 py-2 text-xs shadow-lg",
2463
+ className: "absolute right-3 bottom-3 left-3 z-40 rounded-lg border border-destructive/20 bg-popover px-3 py-2 text-xs shadow-lg",
2296
2464
  children: [
2297
2465
  /* @__PURE__ */ jsx("div", {
2298
2466
  className: "font-medium",
@@ -2324,6 +2492,69 @@ const SurveyAssistantPanel = ({ language, localizations, ...props }) => /* @__PU
2324
2492
  })
2325
2493
  });
2326
2494
  //#endregion
2495
+ //#region src/ui/survey-assistant-thread-launcher-prompt.tsx
2496
+ const SurveyAssistantThreadLauncherPromptContent = ({ className, disabled = false, modelCatalogApi, modelStorageKey, onError, onModelCatalogError, onSubmitted, onValueChange, placeholder, textareaClassName, textareaProps, textareaRef, threadLauncher, uiContext, value }) => {
2497
+ const catalog = useSurveyAssistantModelCatalog({
2498
+ api: modelCatalogApi,
2499
+ storageKey: modelStorageKey
2500
+ });
2501
+ const [submitting, setSubmitting] = useState(false);
2502
+ useEffect(() => {
2503
+ if (catalog.error) onModelCatalogError?.(catalog.error);
2504
+ }, [catalog.error, onModelCatalogError]);
2505
+ const submit = useCallback(async ({ files, text }) => {
2506
+ if (disabled || submitting) return false;
2507
+ setSubmitting(true);
2508
+ try {
2509
+ await threadLauncher.startThread({
2510
+ files,
2511
+ modelId: catalog.modelId,
2512
+ prompt: text,
2513
+ uiContext
2514
+ });
2515
+ onSubmitted();
2516
+ return true;
2517
+ } catch (error) {
2518
+ onError?.(error);
2519
+ return false;
2520
+ } finally {
2521
+ setSubmitting(false);
2522
+ }
2523
+ }, [
2524
+ catalog.modelId,
2525
+ disabled,
2526
+ onError,
2527
+ onSubmitted,
2528
+ submitting,
2529
+ threadLauncher,
2530
+ uiContext
2531
+ ]);
2532
+ return /* @__PURE__ */ jsx(SurveyAssistantPromptInput, {
2533
+ attachmentsDisabled: submitting,
2534
+ className,
2535
+ disabled: disabled || submitting,
2536
+ modelDisabled: catalog.loading || submitting,
2537
+ modelId: catalog.modelId,
2538
+ models: catalog.models,
2539
+ onModelChange: catalog.selectModel,
2540
+ onSubmit: submit,
2541
+ onValueChange,
2542
+ placeholder,
2543
+ textareaClassName,
2544
+ textareaProps,
2545
+ textareaRef,
2546
+ value
2547
+ });
2548
+ };
2549
+ const SurveyAssistantThreadLauncherPrompt = ({ language, localizations, ...props }) => /* @__PURE__ */ jsx("div", {
2550
+ className: "case-survey-assistant",
2551
+ children: /* @__PURE__ */ jsx(SurveyAssistantLocalizationProvider, {
2552
+ language,
2553
+ localizations,
2554
+ children: /* @__PURE__ */ jsx(SurveyAssistantThreadLauncherPromptContent, { ...props })
2555
+ })
2556
+ });
2557
+ //#endregion
2327
2558
  //#region src/ui/connected-survey-assistant-panel.tsx
2328
2559
  const ConnectedSurveyAssistantPanel = ({ modelCatalogApi, modelStorageKey, onModelCatalogError, onModelChange, ...props }) => {
2329
2560
  const catalog = useSurveyAssistantModelCatalog({
@@ -2342,6 +2573,6 @@ const ConnectedSurveyAssistantPanel = ({ modelCatalogApi, modelStorageKey, onMod
2342
2573
  });
2343
2574
  };
2344
2575
  //#endregion
2345
- export { ConnectedSurveyAssistantPanel, SurveyAssistantLocalizationProvider, SurveyAssistantPanel, defaultSurveyAssistantLocalizations, surveyAssistantEnMessages, surveyAssistantNlMessages, useSurveyAssistantTranslation };
2576
+ export { ConnectedSurveyAssistantPanel, SurveyAssistantLocalizationProvider, SurveyAssistantPanel, SurveyAssistantPromptInput, SurveyAssistantThreadLauncherPrompt, defaultSurveyAssistantLocalizations, surveyAssistantEnMessages, surveyAssistantNlMessages, useSurveyAssistantTranslation };
2346
2577
 
2347
2578
  //# sourceMappingURL=ui.mjs.map