@case-framework/survey-assistant 0.4.0 → 0.6.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 (94) hide show
  1. package/README.md +8 -0
  2. package/dist/attachments-B0hg3vpT.mjs +11 -0
  3. package/dist/attachments-B0hg3vpT.mjs.map +1 -0
  4. package/dist/authoring-references-CW38pYzK.mjs +115 -0
  5. package/dist/authoring-references-CW38pYzK.mjs.map +1 -0
  6. package/dist/{capabilities-CnsVuJ1k.d.mts → capabilities-DVTfogjv.d.mts} +1 -1
  7. package/dist/{capabilities-CnsVuJ1k.d.mts.map → capabilities-DVTfogjv.d.mts.map} +1 -1
  8. package/dist/capabilities-default.d.mts +1 -1
  9. package/dist/capabilities-default.d.mts.map +1 -1
  10. package/dist/capabilities-default.mjs +39 -9
  11. package/dist/capabilities-default.mjs.map +1 -1
  12. package/dist/{constants-CFUWrRtl.mjs → constants-0UHGFcRJ.mjs} +1 -1
  13. package/dist/{constants-CFUWrRtl.mjs.map → constants-0UHGFcRJ.mjs.map} +1 -1
  14. package/dist/{constants-k1b_evIb.d.mts → constants-BMGTgfjv.d.mts} +4 -4
  15. package/dist/{constants-k1b_evIb.d.mts.map → constants-BMGTgfjv.d.mts.map} +1 -1
  16. package/dist/{controller-proxy-jWIPOZAv.mjs → controller-proxy-DMTqer6T.mjs} +1 -1
  17. package/dist/{controller-proxy-jWIPOZAv.mjs.map → controller-proxy-DMTqer6T.mjs.map} +1 -1
  18. package/dist/{controller-proxy-quQjtA7j.d.mts → controller-proxy-mRvez2qw.d.mts} +3 -3
  19. package/dist/{controller-proxy-quQjtA7j.d.mts.map → controller-proxy-mRvez2qw.d.mts.map} +1 -1
  20. package/dist/{digest-Dyjch_sC.d.mts → digest-SJmoYVaK.d.mts} +2 -2
  21. package/dist/{digest-Dyjch_sC.d.mts.map → digest-SJmoYVaK.d.mts.map} +1 -1
  22. package/dist/digest.d.mts +1 -1
  23. package/dist/{engine-BvfUx_Jx.mjs → engine-CQwOAcwK.mjs} +167 -24
  24. package/dist/engine-CQwOAcwK.mjs.map +1 -0
  25. package/dist/engine.d.mts +3 -3
  26. package/dist/engine.mjs +3 -3
  27. package/dist/{fair-guidance-CEdJYccK.mjs → fair-guidance-CRYCRxQm.mjs} +1 -1
  28. package/dist/{fair-guidance-CEdJYccK.mjs.map → fair-guidance-CRYCRxQm.mjs.map} +1 -1
  29. package/dist/{form-authoring-BQh40yDb.mjs → form-authoring-kQDgC5oz.mjs} +1 -1
  30. package/dist/{form-authoring-BQh40yDb.mjs.map → form-authoring-kQDgC5oz.mjs.map} +1 -1
  31. package/dist/{index-biZnRl-h.d.mts → index-B0lxsAzx.d.mts} +10 -6
  32. package/dist/index-B0lxsAzx.d.mts.map +1 -0
  33. package/dist/{index-BDp6S7r9.d.mts → index-BS-Xjk1h.d.mts} +4 -4
  34. package/dist/{index-BDp6S7r9.d.mts.map → index-BS-Xjk1h.d.mts.map} +1 -1
  35. package/dist/{index-r8HVcn5b.d.mts → index-D6nqkz0O.d.mts} +49 -8
  36. package/dist/{index-r8HVcn5b.d.mts.map → index-D6nqkz0O.d.mts.map} +1 -1
  37. package/dist/{index-8x1Eqlnz.d.mts → index-DvTqML-0.d.mts} +10 -5
  38. package/dist/index-DvTqML-0.d.mts.map +1 -0
  39. package/dist/{lifecycle-DWv5CiSu.d.mts → lifecycle-BmGAIUlv.d.mts} +2 -2
  40. package/dist/{lifecycle-DWv5CiSu.d.mts.map → lifecycle-BmGAIUlv.d.mts.map} +1 -1
  41. package/dist/{memory-thread-repository-xBZOog_q.mjs → memory-thread-repository-CjFGVNZh.mjs} +44 -13
  42. package/dist/{memory-thread-repository-xBZOog_q.mjs.map → memory-thread-repository-CjFGVNZh.mjs.map} +1 -1
  43. package/dist/protocol.d.mts +4 -4
  44. package/dist/protocol.mjs +2 -1
  45. package/dist/{react-CY6N7lVi.mjs → react-Dqlmasrs.mjs} +2 -2
  46. package/dist/{react-CY6N7lVi.mjs.map → react-Dqlmasrs.mjs.map} +1 -1
  47. package/dist/react-integration.d.mts +1 -1
  48. package/dist/react-integration.mjs +1 -1
  49. package/dist/react.d.mts +2 -2
  50. package/dist/react.mjs +2 -2
  51. package/dist/{request-context-DixY5DYu.mjs → request-context-BjTHNcDd.mjs} +2 -2
  52. package/dist/{request-context-DixY5DYu.mjs.map → request-context-BjTHNcDd.mjs.map} +1 -1
  53. package/dist/{request-guardrails-h4t6u8mP.mjs → request-guardrails-TmgQtqp4.mjs} +5 -12
  54. package/dist/request-guardrails-TmgQtqp4.mjs.map +1 -0
  55. package/dist/server-agent.d.mts +9 -7
  56. package/dist/server-agent.d.mts.map +1 -1
  57. package/dist/server-agent.mjs +8 -6
  58. package/dist/server-agent.mjs.map +1 -1
  59. package/dist/server-runtime.d.mts +28 -14
  60. package/dist/server-runtime.d.mts.map +1 -1
  61. package/dist/server-runtime.mjs +47 -8
  62. package/dist/server-runtime.mjs.map +1 -1
  63. package/dist/server-tasks.d.mts +1 -1
  64. package/dist/server-tasks.mjs +4 -4
  65. package/dist/server-tools.d.mts +1 -1
  66. package/dist/server-tools.mjs +1 -1
  67. package/dist/server.d.mts +7 -7
  68. package/dist/server.mjs +6 -6
  69. package/dist/storage-postgres.d.mts +1 -1
  70. package/dist/storage-postgres.mjs +4 -1
  71. package/dist/storage-postgres.mjs.map +1 -1
  72. package/dist/{tasks-BWmTMXoK.d.mts → tasks-DxzD25Zc.d.mts} +16 -4
  73. package/dist/tasks-DxzD25Zc.d.mts.map +1 -0
  74. package/dist/{thread-documents-Dvo6XS7o.d.mts → thread-documents-rmd6nyX9.d.mts} +1 -1
  75. package/dist/{thread-documents-Dvo6XS7o.d.mts.map → thread-documents-rmd6nyX9.d.mts.map} +1 -1
  76. package/dist/{thread-handlers-D8gkLfU6.d.mts → thread-handlers-AbQbt4sX.d.mts} +13 -6
  77. package/dist/{thread-handlers-D8gkLfU6.d.mts.map → thread-handlers-AbQbt4sX.d.mts.map} +1 -1
  78. package/dist/{tools-TXI8bfzZ.mjs → tools-CFh35R4d.mjs} +14 -6
  79. package/dist/tools-CFh35R4d.mjs.map +1 -0
  80. package/dist/ui.css +1 -1
  81. package/dist/ui.d.mts +3 -3
  82. package/dist/ui.d.mts.map +1 -1
  83. package/dist/ui.mjs +198 -83
  84. package/dist/ui.mjs.map +1 -1
  85. package/docs/integration.md +37 -3
  86. package/package.json +2 -2
  87. package/dist/authoring-references-yyYC4Ydi.mjs +0 -98
  88. package/dist/authoring-references-yyYC4Ydi.mjs.map +0 -1
  89. package/dist/engine-BvfUx_Jx.mjs.map +0 -1
  90. package/dist/index-8x1Eqlnz.d.mts.map +0 -1
  91. package/dist/index-biZnRl-h.d.mts.map +0 -1
  92. package/dist/request-guardrails-h4t6u8mP.mjs.map +0 -1
  93. package/dist/tasks-BWmTMXoK.d.mts.map +0 -1
  94. package/dist/tools-TXI8bfzZ.mjs.map +0 -1
package/dist/ui.mjs CHANGED
@@ -1,10 +1,11 @@
1
1
  "use client";
2
2
  import { jt as parseSurveyAssistantError, l as surveyAssistantTurnFinalSchema, n as SURVEY_ASSISTANT_TURN_FINAL_PART_TYPE } from "./protocol-DJUP4gc0.mjs";
3
- import { f as unwrapSurveyAssistantToolOutput, i as createSurveyAssistantThreadClient, l as parseSurveyChangeSet, p as isSurveyAssistantTurnActive, r as useSurveyAssistantModelCatalog, t as useSurveyAssistantSession } from "./react-CY6N7lVi.mjs";
4
- import { o as connectSurveyAssistantThreadLauncher } from "./controller-proxy-jWIPOZAv.mjs";
3
+ import { n as isSurveyAssistantAttachmentMediaType, t as SURVEY_ASSISTANT_ATTACHMENT_MEDIA_TYPES } from "./attachments-B0hg3vpT.mjs";
4
+ import { f as unwrapSurveyAssistantToolOutput, i as createSurveyAssistantThreadClient, l as parseSurveyChangeSet, p as isSurveyAssistantTurnActive, r as useSurveyAssistantModelCatalog, t as useSurveyAssistantSession } from "./react-Dqlmasrs.mjs";
5
+ import { o as connectSurveyAssistantThreadLauncher } from "./controller-proxy-DMTqer6T.mjs";
5
6
  import { getToolName, isFileUIPart, isReasoningUIPart, isToolUIPart } from "ai";
6
7
  import { createContext, createElement, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
7
- import { Collapsible, Select, Slot } from "radix-ui";
8
+ import { Collapsible, Select, Slot, Tooltip } from "radix-ui";
8
9
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
10
  import { clsx } from "clsx";
10
11
  import { twMerge } from "tailwind-merge";
@@ -123,9 +124,11 @@ const surveyAssistantEnMessages = {
123
124
  },
124
125
  input: {
125
126
  placeholder: "Ask about the survey...",
126
- attachFiles: "Attach files",
127
- dropFiles: "Drop images or PDFs to attach",
128
- unsupportedFiles: "Unsupported file type: {filenames}",
127
+ attachFiles: "Attach a PDF, PNG, or JPG",
128
+ dropFiles: "Drop a PDF, PNG, or JPG to attach",
129
+ supportedFiles: "Supported: PDF, PNG, and JPG. Export Word documents as PDF.",
130
+ unsupportedFiles: "{filenames} can't be attached. Supported formats: PDF, PNG, and JPG.",
131
+ wordFilesUnsupported: "Word documents aren't supported. Export {filenames} as PDF, then attach the PDF.",
129
132
  removeFile: "Remove {filename}",
130
133
  send: "Send message",
131
134
  stop: "Stop response",
@@ -338,9 +341,11 @@ const surveyAssistantNlMessages = {
338
341
  },
339
342
  input: {
340
343
  placeholder: "Vraag iets over de survey...",
341
- attachFiles: "Bestanden toevoegen",
342
- dropFiles: "Sleep afbeeldingen of pdf's hierheen om ze toe te voegen",
343
- unsupportedFiles: "Niet-ondersteund bestandstype: {filenames}",
344
+ attachFiles: "Een pdf-, png- of jpg-bestand toevoegen",
345
+ dropFiles: "Sleep een pdf-, png- of jpg-bestand hierheen om het toe te voegen",
346
+ supportedFiles: "Ondersteund: pdf, png en jpg. Exporteer Word-documenten als pdf.",
347
+ unsupportedFiles: "{filenames} kan niet worden toegevoegd. Ondersteunde formaten: pdf, png en jpg.",
348
+ wordFilesUnsupported: "Word-documenten worden niet ondersteund. Exporteer {filenames} als pdf en voeg daarna de pdf toe.",
344
349
  removeFile: "{filename} verwijderen",
345
350
  send: "Bericht verzenden",
346
351
  stop: "Antwoord stoppen",
@@ -771,9 +776,39 @@ const MessageContent = ({ children, className, ...props }) => /* @__PURE__ */ js
771
776
  children
772
777
  });
773
778
  //#endregion
779
+ //#region src/ui/internal/tooltip.tsx
780
+ const TooltipProvider = Tooltip.Provider;
781
+ const Tooltip$1 = Tooltip.Root;
782
+ const TooltipTrigger = Tooltip.Trigger;
783
+ const TooltipContent = ({ className, sideOffset = 6, ...props }) => /* @__PURE__ */ jsx(Tooltip.Portal, { children: /* @__PURE__ */ jsx("div", {
784
+ className: "case-survey-assistant case-survey-assistant-portal",
785
+ children: /* @__PURE__ */ jsx(Tooltip.Content, {
786
+ className: cn("z-50 max-w-64 rounded-md bg-foreground px-3 py-1.5 text-center text-background text-xs shadow-md data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0", className),
787
+ sideOffset,
788
+ ...props
789
+ })
790
+ }) });
791
+ //#endregion
774
792
  //#region src/ui/internal/prompt-input.tsx
775
- const SUPPORTED_FILE_ACCEPT = "image/*,application/pdf";
776
- const isSupportedFile = (file) => file.type.startsWith("image/") || file.type === "application/pdf";
793
+ const SUPPORTED_FILE_ACCEPT = [
794
+ ".pdf",
795
+ ".png",
796
+ ".jpg",
797
+ ".jpeg",
798
+ ...SURVEY_ASSISTANT_ATTACHMENT_MEDIA_TYPES
799
+ ].join(",");
800
+ const WORD_DOCUMENT_MEDIA_TYPES = new Set([
801
+ "application/msword",
802
+ "application/vnd.ms-word",
803
+ "application/vnd.ms-word.document.macroenabled.12",
804
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
805
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.template"
806
+ ]);
807
+ const getUnsupportedAttachmentKind = ({ name, type }) => {
808
+ if (isSurveyAssistantAttachmentMediaType(type)) return null;
809
+ if (WORD_DOCUMENT_MEDIA_TYPES.has(type.toLowerCase()) || /\.(?:doc|docm|docx|dot|dotm|dotx)$/i.test(name)) return "word";
810
+ return "other";
811
+ };
777
812
  const formatFileSize = (size) => {
778
813
  if (size < 1024 * 1024) return `${Math.max(1, Math.round(size / 1024))} KB`;
779
814
  return `${(size / (1024 * 1024)).toFixed(size < 10 * 1024 * 1024 ? 1 : 0)} MB`;
@@ -801,7 +836,7 @@ const PromptInput = ({ attachmentsDisabled = false, children, className, onDragE
801
836
  const [uncontrolledValue, setUncontrolledValue] = useState("");
802
837
  const [files, setFiles] = useState([]);
803
838
  const [isDraggingFiles, setIsDraggingFiles] = useState(false);
804
- const [unsupportedFileNames, setUnsupportedFileNames] = useState([]);
839
+ const [unsupportedFiles, setUnsupportedFiles] = useState([]);
805
840
  const fileDragDepthRef = useRef(0);
806
841
  const t = useSurveyAssistantTranslation();
807
842
  const value = controlledValue ?? uncontrolledValue;
@@ -811,10 +846,16 @@ const PromptInput = ({ attachmentsDisabled = false, children, className, onDragE
811
846
  }, [controlledValue, onValueChange]);
812
847
  const addFiles = useCallback((fileList) => {
813
848
  const nextFiles = Array.from(fileList ?? []);
814
- const supportedFiles = nextFiles.filter(isSupportedFile);
815
- const unsupportedFiles = nextFiles.filter((file) => !isSupportedFile(file));
849
+ const supportedFiles = nextFiles.filter((file) => getUnsupportedAttachmentKind(file) === null);
850
+ const rejectedFiles = nextFiles.flatMap((file) => {
851
+ const kind = getUnsupportedAttachmentKind(file);
852
+ return kind ? [{
853
+ kind,
854
+ name: file.name
855
+ }] : [];
856
+ });
816
857
  setFiles((currentFiles) => [...currentFiles, ...supportedFiles]);
817
- setUnsupportedFileNames(unsupportedFiles.map((file) => file.name));
858
+ setUnsupportedFiles(rejectedFiles);
818
859
  }, []);
819
860
  const removeFile = useCallback((index) => {
820
861
  setFiles((currentFiles) => currentFiles.filter((_, fileIndex) => fileIndex !== index));
@@ -825,12 +866,12 @@ const PromptInput = ({ attachmentsDisabled = false, children, className, onDragE
825
866
  files,
826
867
  addFiles,
827
868
  removeFile,
828
- unsupportedFileNames
869
+ unsupportedFiles
829
870
  }), [
830
871
  addFiles,
831
872
  files,
832
873
  removeFile,
833
- unsupportedFileNames,
874
+ unsupportedFiles,
834
875
  value
835
876
  ]);
836
877
  const handleSubmit = useCallback(async (event) => {
@@ -843,7 +884,7 @@ const PromptInput = ({ attachmentsDisabled = false, children, className, onDragE
843
884
  }, event) === false) return;
844
885
  setValue("");
845
886
  setFiles([]);
846
- setUnsupportedFileNames([]);
887
+ setUnsupportedFiles([]);
847
888
  }, [
848
889
  files,
849
890
  onSubmit,
@@ -903,39 +944,48 @@ const PromptInput = ({ attachmentsDisabled = false, children, className, onDragE
903
944
  });
904
945
  };
905
946
  const PromptInputBody = ({ children, className, ...props }) => {
906
- const { files, removeFile, unsupportedFileNames } = usePromptInput();
947
+ const { files, removeFile, unsupportedFiles } = usePromptInput();
907
948
  const t = useSurveyAssistantTranslation();
949
+ const unsupportedWordFileNames = unsupportedFiles.filter((file) => file.kind === "word").map((file) => file.name);
950
+ const unsupportedOtherFileNames = unsupportedFiles.filter((file) => file.kind === "other").map((file) => file.name);
908
951
  return /* @__PURE__ */ jsxs("div", {
909
952
  className: cn("contents", className),
910
953
  ...props,
911
- children: [children, (files.length > 0 || unsupportedFileNames.length > 0) && /* @__PURE__ */ jsxs("div", {
954
+ children: [children, (files.length > 0 || unsupportedFiles.length > 0) && /* @__PURE__ */ jsxs("div", {
912
955
  className: "flex flex-wrap gap-2 px-3 pb-2",
913
- children: [files.map((file, index) => /* @__PURE__ */ jsxs("div", {
914
- className: "inline-flex max-w-full items-center gap-2 rounded-md border border-border bg-muted px-2 py-1 text-xs",
915
- children: [
916
- /* @__PURE__ */ jsx(FileIcon, { className: "size-3.5 shrink-0 text-muted-foreground" }),
917
- /* @__PURE__ */ jsx("span", {
918
- className: "min-w-0 truncate",
919
- children: file.name
920
- }),
921
- /* @__PURE__ */ jsx("span", {
922
- className: "shrink-0 text-muted-foreground",
923
- children: formatFileSize(file.size)
924
- }),
925
- /* @__PURE__ */ jsxs("button", {
926
- className: "shrink-0 rounded-sm text-muted-foreground hover:text-foreground",
927
- onClick: () => removeFile(index),
928
- type: "button",
929
- children: [/* @__PURE__ */ jsx(XIcon, { className: "size-3.5" }), /* @__PURE__ */ jsx("span", {
930
- className: "sr-only",
931
- children: t("input.removeFile", { filename: file.name })
932
- })]
933
- })
934
- ]
935
- }, `${file.name}-${file.lastModified}-${index}`)), unsupportedFileNames.length > 0 && /* @__PURE__ */ jsx("div", {
936
- className: "text-destructive text-xs",
937
- children: t("input.unsupportedFiles", { filenames: unsupportedFileNames.join(", ") })
938
- })]
956
+ children: [
957
+ files.map((file, index) => /* @__PURE__ */ jsxs("div", {
958
+ className: "inline-flex max-w-full items-center gap-2 rounded-md border border-border bg-muted px-2 py-1 text-xs",
959
+ children: [
960
+ /* @__PURE__ */ jsx(FileIcon, { className: "size-3.5 shrink-0 text-muted-foreground" }),
961
+ /* @__PURE__ */ jsx("span", {
962
+ className: "min-w-0 truncate",
963
+ children: file.name
964
+ }),
965
+ /* @__PURE__ */ jsx("span", {
966
+ className: "shrink-0 text-muted-foreground",
967
+ children: formatFileSize(file.size)
968
+ }),
969
+ /* @__PURE__ */ jsxs("button", {
970
+ className: "shrink-0 rounded-sm text-muted-foreground hover:text-foreground",
971
+ onClick: () => removeFile(index),
972
+ type: "button",
973
+ children: [/* @__PURE__ */ jsx(XIcon, { className: "size-3.5" }), /* @__PURE__ */ jsx("span", {
974
+ className: "sr-only",
975
+ children: t("input.removeFile", { filename: file.name })
976
+ })]
977
+ })
978
+ ]
979
+ }, `${file.name}-${file.lastModified}-${index}`)),
980
+ unsupportedWordFileNames.length > 0 && /* @__PURE__ */ jsx("div", {
981
+ className: "text-destructive text-xs",
982
+ children: t("input.wordFilesUnsupported", { filenames: unsupportedWordFileNames.join(", ") })
983
+ }),
984
+ unsupportedOtherFileNames.length > 0 && /* @__PURE__ */ jsx("div", {
985
+ className: "text-destructive text-xs",
986
+ children: t("input.unsupportedFiles", { filenames: unsupportedOtherFileNames.join(", ") })
987
+ })
988
+ ]
939
989
  })]
940
990
  });
941
991
  };
@@ -964,28 +1014,38 @@ const PromptInputFooter = ({ className, ...props }) => /* @__PURE__ */ jsx("div"
964
1014
  const PromptInputAttachmentButton = ({ accept = SUPPORTED_FILE_ACCEPT, className, disabled, onChange, ...props }) => {
965
1015
  const { addFiles } = usePromptInput();
966
1016
  const t = useSurveyAssistantTranslation();
967
- return /* @__PURE__ */ jsxs("label", {
968
- className: cn("inline-flex size-8 cursor-pointer items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground", disabled && "pointer-events-none cursor-default opacity-50", className),
969
- children: [
970
- /* @__PURE__ */ jsx(PaperclipIcon, { className: "size-4" }),
971
- /* @__PURE__ */ jsx("span", {
972
- className: "sr-only",
973
- children: t("input.attachFiles")
974
- }),
975
- /* @__PURE__ */ jsx("input", {
976
- accept,
977
- className: "sr-only",
978
- disabled,
979
- multiple: true,
980
- onChange: (event) => {
981
- addFiles(event.currentTarget.files);
982
- onChange?.(event);
983
- event.currentTarget.value = "";
984
- },
985
- type: "file",
986
- ...props
1017
+ return /* @__PURE__ */ jsx(TooltipProvider, {
1018
+ delayDuration: 300,
1019
+ children: /* @__PURE__ */ jsxs(Tooltip$1, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
1020
+ asChild: true,
1021
+ children: /* @__PURE__ */ jsxs("label", {
1022
+ className: cn("inline-flex size-8 cursor-pointer items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground", disabled && "pointer-events-none cursor-default opacity-50", className),
1023
+ children: [
1024
+ /* @__PURE__ */ jsx(PaperclipIcon, { className: "size-4" }),
1025
+ /* @__PURE__ */ jsx("span", {
1026
+ className: "sr-only",
1027
+ children: t("input.attachFiles")
1028
+ }),
1029
+ /* @__PURE__ */ jsx("input", {
1030
+ accept,
1031
+ className: "sr-only",
1032
+ disabled,
1033
+ multiple: true,
1034
+ onChange: (event) => {
1035
+ addFiles(event.currentTarget.files);
1036
+ onChange?.(event);
1037
+ event.currentTarget.value = "";
1038
+ },
1039
+ type: "file",
1040
+ ...props
1041
+ })
1042
+ ]
987
1043
  })
988
- ]
1044
+ }), /* @__PURE__ */ jsx(TooltipContent, {
1045
+ align: "start",
1046
+ side: "top",
1047
+ children: t("input.supportedFiles")
1048
+ })] })
989
1049
  });
990
1050
  };
991
1051
  const PromptInputSubmit = ({ className, status, onStop, onClick, ...props }) => {
@@ -1353,7 +1413,7 @@ const Response = ({ children, className, controls = false, plugins, ...props })
1353
1413
  code: plugins?.code ?? codeHighlighter
1354
1414
  }), [plugins]);
1355
1415
  return /* @__PURE__ */ jsx(Streamdown, {
1356
- 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),
1416
+ 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=inline-code]:text-[length:inherit] **:data-[streamdown=inline-code]:leading-[inherit]", "**: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),
1357
1417
  controls,
1358
1418
  plugins: responsePlugins,
1359
1419
  ...props,
@@ -1408,6 +1468,13 @@ const getPersistedMessageTurnStatus = (message) => {
1408
1468
  }
1409
1469
  };
1410
1470
  //#endregion
1471
+ //#region src/ui/initial-thread-load.ts
1472
+ const getSurveyAssistantInitialThreadLoadState = ({ error, hasSelectedThread, loading }) => {
1473
+ if (loading) return "loading";
1474
+ if (!hasSelectedThread) return error ? "error" : "loading";
1475
+ return null;
1476
+ };
1477
+ //#endregion
1411
1478
  //#region src/ui/survey-assistant-panel.tsx
1412
1479
  const isRootSurveyItem = (item) => typeof item.config === "object" && item.config !== null && item.config.isRoot === true;
1413
1480
  const getAssistantEmptyStateContext = (controller) => {
@@ -1902,7 +1969,7 @@ const SurveyAssistantHeaderIdentity = ({ indicatorClassName, status, title }) =>
1902
1969
  })]
1903
1970
  })]
1904
1971
  });
1905
- const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = false, hasEarlierMessages, initialLaunch, initialMessages, modelId, models, onInitialLaunchHandled, onModelChange, onFirstSubmit, onLoadEarlierMessages, onTurnSettled, onApplyReceipts, onSettleApply, onReviewChangeSets, onTurnActiveChange, resourceId, surveyId, threadControls, threadId, threadTitle }) => {
1972
+ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = false, focusPromptOnMount = false, hasEarlierMessages, initialLaunch, initialMessages, modelId, models, onInitialLaunchHandled, onModelChange, onFirstSubmit, onLoadEarlierMessages, onTurnSettled, onApplyReceipts, onSettleApply, onReviewChangeSets, onTurnActiveChange, resourceId, surveyId, threadControls, threadId, threadTitle }) => {
1906
1973
  const t = useSurveyAssistantTranslation();
1907
1974
  const emptyStateContext = getAssistantEmptyStateContext(controller);
1908
1975
  const assistantQuickPrompts = getAssistantQuickPrompts(t, emptyStateContext);
@@ -2187,6 +2254,7 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
2187
2254
  onSubmit: handleSubmit,
2188
2255
  status,
2189
2256
  textareaClassName: "min-h-11 max-h-28 px-3 pt-2.5 pb-1.5 transition-colors disabled:placeholder:text-muted-foreground/60",
2257
+ textareaProps: { autoFocus: focusPromptOnMount },
2190
2258
  textareaRef: promptTextareaRef
2191
2259
  })
2192
2260
  })
@@ -2230,8 +2298,10 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2230
2298
  const [editingTitle, setEditingTitle] = useState("");
2231
2299
  const [threadError, setThreadError] = useState(null);
2232
2300
  const [loadingThread, setLoadingThread] = useState(true);
2301
+ const [threadLoadAttempt, setThreadLoadAttempt] = useState(0);
2233
2302
  const [assistantTurnActive, setAssistantTurnActive] = useState(false);
2234
2303
  const [initialLaunch, setInitialLaunch] = useState();
2304
+ const [promptFocusThreadId, setPromptFocusThreadId] = useState();
2235
2305
  const historyControlsRef = useRef(null);
2236
2306
  const loadingAllThreadsRef = useRef(false);
2237
2307
  const selectModel = useCallback((modelId) => {
@@ -2239,6 +2309,7 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2239
2309
  onModelChange(modelId);
2240
2310
  }, [models, onModelChange]);
2241
2311
  const selectThread = useCallback(async (thread, reportError = true) => {
2312
+ setPromptFocusThreadId(void 0);
2242
2313
  setLoadingThread(true);
2243
2314
  setThreadError(null);
2244
2315
  try {
@@ -2265,6 +2336,7 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2265
2336
  setThreadError(null);
2266
2337
  try {
2267
2338
  const thread = await threadClient.create(launch?.title);
2339
+ setPromptFocusThreadId(launch ? void 0 : thread.id);
2268
2340
  setThreads((current) => mergeThreads(current, [thread]));
2269
2341
  setSelectedThread(thread);
2270
2342
  setInitialMessages([]);
@@ -2317,8 +2389,14 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2317
2389
  selectThread,
2318
2390
  storageKey,
2319
2391
  t,
2320
- threadClient
2392
+ threadClient,
2393
+ threadLoadAttempt
2321
2394
  ]);
2395
+ const retryInitialThreadLoad = useCallback(() => {
2396
+ setThreadError(null);
2397
+ setLoadingThread(true);
2398
+ setThreadLoadAttempt((attempt) => attempt + 1);
2399
+ }, []);
2322
2400
  const loadOlderThreads = useCallback(async () => {
2323
2401
  if (!nextThreadCursor || loadingOlderThreads) return;
2324
2402
  setLoadingOlderThreads(true);
@@ -2543,6 +2621,13 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2543
2621
  commitEditingThread,
2544
2622
  historyOpen
2545
2623
  ]);
2624
+ useEffect(() => {
2625
+ if (!loadingThread && selectedThread?.id === promptFocusThreadId) setPromptFocusThreadId(void 0);
2626
+ }, [
2627
+ loadingThread,
2628
+ promptFocusThreadId,
2629
+ selectedThread?.id
2630
+ ]);
2546
2631
  useEffect(() => {
2547
2632
  const handleFocus = () => {
2548
2633
  refreshThreadList(false);
@@ -2550,20 +2635,49 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2550
2635
  window.addEventListener("focus", handleFocus);
2551
2636
  return () => window.removeEventListener("focus", handleFocus);
2552
2637
  }, [refreshThreadList]);
2553
- if (loadingThread || !selectedThread) return /* @__PURE__ */ jsxs("aside", {
2554
- className: cn("flex h-full min-h-0 flex-col overflow-visible bg-background", !embedded && "rounded-xl border border-border shadow-sm"),
2555
- children: [/* @__PURE__ */ jsx("header", {
2556
- className: "box-content flex h-9 shrink-0 items-center border-b border-border bg-background px-2.5",
2557
- children: /* @__PURE__ */ jsx(SurveyAssistantHeaderIdentity, {
2558
- indicatorClassName: "animate-pulse bg-blue-500",
2559
- status: t("panel.loadingChats"),
2560
- title: t("panel.title")
2561
- })
2562
- }), /* @__PURE__ */ jsxs("div", {
2563
- className: "flex min-h-0 flex-1 items-center justify-center text-muted-foreground text-sm",
2564
- children: [/* @__PURE__ */ jsx(LoaderCircleIcon, { className: "mr-2 size-4 animate-spin" }), t("panel.loadingChats")]
2565
- })]
2638
+ const initialThreadLoadState = getSurveyAssistantInitialThreadLoadState({
2639
+ error: threadError,
2640
+ hasSelectedThread: Boolean(selectedThread),
2641
+ loading: loadingThread
2566
2642
  });
2643
+ if (initialThreadLoadState || !selectedThread) {
2644
+ const initialLoadFailed = initialThreadLoadState === "error";
2645
+ return /* @__PURE__ */ jsxs("aside", {
2646
+ className: cn("flex h-full min-h-0 flex-col overflow-visible bg-background", !embedded && "rounded-xl border border-border shadow-sm"),
2647
+ children: [/* @__PURE__ */ jsx("header", {
2648
+ className: "box-content flex h-9 shrink-0 items-center border-b border-border bg-background px-2.5",
2649
+ children: /* @__PURE__ */ jsx(SurveyAssistantHeaderIdentity, {
2650
+ indicatorClassName: initialLoadFailed ? "bg-destructive" : "animate-pulse bg-blue-500",
2651
+ status: initialLoadFailed ? t("panel.controlsUnavailable") : t("panel.loadingChats"),
2652
+ title: t("panel.title")
2653
+ })
2654
+ }), /* @__PURE__ */ jsx("div", {
2655
+ className: "flex min-h-0 flex-1 items-center justify-center p-6 text-sm",
2656
+ children: initialLoadFailed ? /* @__PURE__ */ jsxs("div", {
2657
+ className: "max-w-sm text-center",
2658
+ children: [
2659
+ /* @__PURE__ */ jsx("div", {
2660
+ className: "font-medium text-foreground",
2661
+ children: t("panel.controlsUnavailable")
2662
+ }),
2663
+ /* @__PURE__ */ jsx("div", {
2664
+ className: "mt-1 text-muted-foreground",
2665
+ children: threadError
2666
+ }),
2667
+ /* @__PURE__ */ jsx("button", {
2668
+ className: "mt-3 rounded-md border border-border bg-background px-3 py-1.5 font-medium text-foreground hover:bg-muted",
2669
+ onClick: retryInitialThreadLoad,
2670
+ type: "button",
2671
+ children: t("errors.retry")
2672
+ })
2673
+ ]
2674
+ }) : /* @__PURE__ */ jsxs("div", {
2675
+ className: "flex items-center text-muted-foreground",
2676
+ children: [/* @__PURE__ */ jsx(LoaderCircleIcon, { className: "mr-2 size-4 animate-spin" }), t("panel.loadingChats")]
2677
+ })
2678
+ })]
2679
+ });
2680
+ }
2567
2681
  const normalizedThreadSearch = threadSearch.trim().toLocaleLowerCase();
2568
2682
  const visibleThreads = normalizedThreadSearch ? threads.filter((thread) => thread.title.toLocaleLowerCase().includes(normalizedThreadSearch)) : threads;
2569
2683
  const threadControls = /* @__PURE__ */ jsxs("div", {
@@ -2685,6 +2799,7 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2685
2799
  children: [/* @__PURE__ */ createElement(SurveyAssistantChatPanel, {
2686
2800
  ...sessionProps,
2687
2801
  embedded,
2802
+ focusPromptOnMount: selectedThread.id === promptFocusThreadId,
2688
2803
  hasEarlierMessages: Boolean(nextMessageCursor),
2689
2804
  initialLaunch: initialLaunch?.input,
2690
2805
  initialMessages,