@case-framework/survey-assistant 0.2.3 → 0.4.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 (87) hide show
  1. package/README.md +12 -4
  2. package/dist/{authoring-references-ChDTVnms.mjs → authoring-references-yyYC4Ydi.mjs} +2 -2
  3. package/dist/{authoring-references-ChDTVnms.mjs.map → authoring-references-yyYC4Ydi.mjs.map} +1 -1
  4. package/dist/capabilities-default.d.mts +1 -1
  5. package/dist/capabilities-default.mjs +1 -1
  6. package/dist/{constants-BK1-g6pR.d.mts → constants-k1b_evIb.d.mts} +7 -4
  7. package/dist/constants-k1b_evIb.d.mts.map +1 -0
  8. package/dist/{controller-proxy-DY36XFV3.mjs → controller-proxy-jWIPOZAv.mjs} +18 -8
  9. package/dist/controller-proxy-jWIPOZAv.mjs.map +1 -0
  10. package/dist/{controller-proxy-CIDRd8Wu.d.mts → controller-proxy-quQjtA7j.d.mts} +5 -2
  11. package/dist/controller-proxy-quQjtA7j.d.mts.map +1 -0
  12. package/dist/{engine-CWTuN3o2.mjs → engine-BvfUx_Jx.mjs} +85 -12
  13. package/dist/engine-BvfUx_Jx.mjs.map +1 -0
  14. package/dist/engine.d.mts +2 -2
  15. package/dist/engine.mjs +3 -3
  16. package/dist/{form-authoring-DCd9XfXf.mjs → form-authoring-BQh40yDb.mjs} +2 -2
  17. package/dist/{form-authoring-DCd9XfXf.mjs.map → form-authoring-BQh40yDb.mjs.map} +1 -1
  18. package/dist/index-8x1Eqlnz.d.mts +741 -0
  19. package/dist/index-8x1Eqlnz.d.mts.map +1 -0
  20. package/dist/{index-SPHf3OoJ.d.mts → index-BDp6S7r9.d.mts} +14 -5
  21. package/dist/index-BDp6S7r9.d.mts.map +1 -0
  22. package/dist/{index-Bp5W_oPa.d.mts → index-biZnRl-h.d.mts} +26 -24
  23. package/dist/index-biZnRl-h.d.mts.map +1 -0
  24. package/dist/{index-CPOQgkTN.d.mts → index-r8HVcn5b.d.mts} +9 -4
  25. package/dist/index-r8HVcn5b.d.mts.map +1 -0
  26. package/dist/lifecycle-DWv5CiSu.d.mts +523 -0
  27. package/dist/lifecycle-DWv5CiSu.d.mts.map +1 -0
  28. package/dist/{memory-thread-repository-acbz6HZJ.mjs → memory-thread-repository-xBZOog_q.mjs} +49 -21
  29. package/dist/memory-thread-repository-xBZOog_q.mjs.map +1 -0
  30. package/dist/{protocol-DATvkr1S.mjs → protocol-DJUP4gc0.mjs} +10 -4
  31. package/dist/protocol-DJUP4gc0.mjs.map +1 -0
  32. package/dist/protocol.d.mts +2 -1
  33. package/dist/protocol.mjs +1 -1
  34. package/dist/{react-3kSE6WtS.mjs → react-CY6N7lVi.mjs} +30 -8
  35. package/dist/react-CY6N7lVi.mjs.map +1 -0
  36. package/dist/react-integration.d.mts +1 -1
  37. package/dist/react-integration.mjs +1 -1
  38. package/dist/react.d.mts +2 -2
  39. package/dist/react.mjs +2 -2
  40. package/dist/{request-context-BEjnaaFr.mjs → request-context-DixY5DYu.mjs} +3 -3
  41. package/dist/request-context-DixY5DYu.mjs.map +1 -0
  42. package/dist/server-agent.d.mts +26 -24
  43. package/dist/server-agent.d.mts.map +1 -1
  44. package/dist/server-agent.mjs +7 -6
  45. package/dist/server-agent.mjs.map +1 -1
  46. package/dist/server-runtime.d.mts +341 -8
  47. package/dist/server-runtime.d.mts.map +1 -1
  48. package/dist/server-runtime.mjs +321 -23
  49. package/dist/server-runtime.mjs.map +1 -1
  50. package/dist/server-tasks.d.mts +1 -1
  51. package/dist/server-tasks.mjs +8 -6
  52. package/dist/server-tasks.mjs.map +1 -1
  53. package/dist/server-tools.d.mts +1 -1
  54. package/dist/server-tools.mjs +1 -1
  55. package/dist/server.d.mts +8 -8
  56. package/dist/server.d.mts.map +1 -1
  57. package/dist/server.mjs +3 -3
  58. package/dist/storage-postgres.d.mts +1 -1
  59. package/dist/{tasks-DIZgr4nD.d.mts → tasks-BWmTMXoK.d.mts} +3 -3
  60. package/dist/{tasks-DIZgr4nD.d.mts.map → tasks-BWmTMXoK.d.mts.map} +1 -1
  61. package/dist/{thread-documents-C3kf0Vds.d.mts → thread-documents-Dvo6XS7o.d.mts} +1 -1
  62. package/dist/{thread-documents-C3kf0Vds.d.mts.map → thread-documents-Dvo6XS7o.d.mts.map} +1 -1
  63. package/dist/{thread-handlers-DZ1WsorO.d.mts → thread-handlers-D8gkLfU6.d.mts} +15 -7
  64. package/dist/{thread-handlers-DZ1WsorO.d.mts.map → thread-handlers-D8gkLfU6.d.mts.map} +1 -1
  65. package/dist/{tools-GDd5_GUh.mjs → tools-TXI8bfzZ.mjs} +28 -9
  66. package/dist/tools-TXI8bfzZ.mjs.map +1 -0
  67. package/dist/ui.css +2 -2
  68. package/dist/ui.d.mts +94 -10
  69. package/dist/ui.d.mts.map +1 -1
  70. package/dist/ui.mjs +561 -203
  71. package/dist/ui.mjs.map +1 -1
  72. package/docs/integration.md +84 -63
  73. package/package.json +25 -19
  74. package/dist/constants-BK1-g6pR.d.mts.map +0 -1
  75. package/dist/controller-proxy-CIDRd8Wu.d.mts.map +0 -1
  76. package/dist/controller-proxy-DY36XFV3.mjs.map +0 -1
  77. package/dist/engine-CWTuN3o2.mjs.map +0 -1
  78. package/dist/index-Bp5W_oPa.d.mts.map +0 -1
  79. package/dist/index-CPOQgkTN.d.mts.map +0 -1
  80. package/dist/index-EDL1V168.d.mts +0 -1257
  81. package/dist/index-EDL1V168.d.mts.map +0 -1
  82. package/dist/index-SPHf3OoJ.d.mts.map +0 -1
  83. package/dist/memory-thread-repository-acbz6HZJ.mjs.map +0 -1
  84. package/dist/protocol-DATvkr1S.mjs.map +0 -1
  85. package/dist/react-3kSE6WtS.mjs.map +0 -1
  86. package/dist/request-context-BEjnaaFr.mjs.map +0 -1
  87. package/dist/tools-GDd5_GUh.mjs.map +0 -1
package/dist/ui.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
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-3kSE6WtS.mjs";
4
- import { o as connectSurveyAssistantThreadLauncher } from "./controller-proxy-DY36XFV3.mjs";
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";
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";
@@ -9,7 +9,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
9
  import { clsx } from "clsx";
10
10
  import { twMerge } from "tailwind-merge";
11
11
  import { useControllableState } from "@radix-ui/react-use-controllable-state";
12
- import { ArrowDownIcon, ArrowUpIcon, BotIcon, BrainIcon, CheckCircleIcon, CheckIcon, ChevronDownIcon, ChevronRightIcon, ChevronUpIcon, ClockIcon, DotIcon, FileIcon, HistoryIcon, ListChecksIcon, LoaderCircleIcon, MessageSquarePlusIcon, PaperclipIcon, PencilIcon, PlusIcon, SparklesIcon, SquareIcon, Trash2Icon, WandSparklesIcon, WrenchIcon, XCircleIcon, XIcon } from "lucide-react";
12
+ import { ArrowDownIcon, ArrowUpIcon, BotIcon, BrainIcon, CheckCircleIcon, CheckIcon, ChevronDownIcon, ChevronRightIcon, ChevronUpIcon, ClockIcon, DotIcon, FileIcon, HistoryIcon, ListChecksIcon, LoaderCircleIcon, MessageSquarePlusIcon, PaperclipIcon, PencilIcon, PlusIcon, SearchIcon, SparklesIcon, SquareIcon, Trash2Icon, WandSparklesIcon, WrenchIcon, XCircleIcon, XIcon } from "lucide-react";
13
13
  import { motion } from "motion/react";
14
14
  import { createLocalizationTranslator, mergeLocalizationCatalogs } from "@case-framework/ui-localization";
15
15
  import { cva } from "class-variance-authority";
@@ -80,13 +80,30 @@ const surveyAssistantEnMessages = {
80
80
  },
81
81
  empty: {
82
82
  title: "What are you working on?",
83
- description: "Draft, inspect, or refine your survey - just describe what you need, or start with one of these."
83
+ description: "Draft, inspect, or refine your survey - just describe what you need, or start with one of these.",
84
+ newSurveyTitle: "Create with the assistant",
85
+ newSurveyDescription: "Describe the survey you need, share a brief, or plan the right questions together."
84
86
  },
85
87
  quickActions: {
88
+ draftSurvey: {
89
+ label: "Draft a survey",
90
+ description: "Create a first draft from your goals",
91
+ prompt: "Help me create a first draft of a new survey. Ask me about its topic, audience, and goals before drafting it."
92
+ },
93
+ useBrief: {
94
+ label: "Use a brief or notes",
95
+ description: "Turn requirements into a survey",
96
+ prompt: "Help me turn a brief or set of notes into a survey. Ask me to paste the text or attach the document."
97
+ },
98
+ planSurvey: {
99
+ label: "Plan the questions",
100
+ description: "Outline the structure before drafting",
101
+ prompt: "Help me plan the questions and structure for a new survey before we draft it."
102
+ },
86
103
  review: {
87
104
  label: "Review survey",
88
105
  description: "Spot issues and suggest improvements",
89
- prompt: "Review the current survey and suggest improvements."
106
+ prompt: "Run a deterministic audit of the current survey, including expression-reference integrity, then review its quality and suggest improvements."
90
107
  },
91
108
  create: {
92
109
  label: "New question",
@@ -97,6 +114,11 @@ const surveyAssistantEnMessages = {
97
114
  label: "Refine wording",
98
115
  description: "Polish respondent-facing text",
99
116
  prompt: "Suggest improvements to the respondent-facing wording."
117
+ },
118
+ refineSelection: {
119
+ label: "Refine selected item",
120
+ description: "Polish the wording in context",
121
+ prompt: "Suggest improvements to the respondent-facing wording of the selected item."
100
122
  }
101
123
  },
102
124
  input: {
@@ -111,6 +133,8 @@ const surveyAssistantEnMessages = {
111
133
  defaultModel: "Default model"
112
134
  },
113
135
  conversation: {
136
+ loadEarlier: "Load earlier messages",
137
+ loadingEarlier: "Loading earlier messages…",
114
138
  noMessages: "No messages yet",
115
139
  startConversation: "Start a conversation to see messages here",
116
140
  scrollToLatest: "Scroll to latest message",
@@ -246,6 +270,11 @@ const surveyAssistantEnMessages = {
246
270
  newChat: "New chat",
247
271
  history: "Chat history",
248
272
  recent: "Recent chats",
273
+ search: "Search chats",
274
+ searching: "Searching older chats…",
275
+ noMatches: "No matching chats",
276
+ loadOlder: "Load older chats",
277
+ loadingOlder: "Loading older chats…",
249
278
  automaticTitle: "Use automatic title",
250
279
  automaticShort: "Auto",
251
280
  rename: "Rename {title}",
@@ -266,13 +295,30 @@ const surveyAssistantNlMessages = {
266
295
  },
267
296
  empty: {
268
297
  title: "Waar werk je aan?",
269
- description: "Maak, controleer of verbeter je survey. Beschrijf wat je nodig hebt of kies een van deze opties."
298
+ description: "Maak, controleer of verbeter je survey. Beschrijf wat je nodig hebt of kies een van deze opties.",
299
+ newSurveyTitle: "Maak iets met de assistent",
300
+ newSurveyDescription: "Beschrijf de survey die je nodig hebt, deel een briefing of plan samen de juiste vragen."
270
301
  },
271
302
  quickActions: {
303
+ draftSurvey: {
304
+ label: "Een survey opstellen",
305
+ description: "Maak een eerste versie vanuit je doelen",
306
+ prompt: "Help me een eerste versie van een nieuwe survey te maken. Vraag me vóór het opstellen naar het onderwerp, de doelgroep en de doelen."
307
+ },
308
+ useBrief: {
309
+ label: "Gebruik een briefing of notities",
310
+ description: "Zet vereisten om in een survey",
311
+ prompt: "Help me een briefing of verzameling notities om te zetten in een survey. Vraag me de tekst te plakken of het document toe te voegen."
312
+ },
313
+ planSurvey: {
314
+ label: "Plan de vragen",
315
+ description: "Schets eerst de structuur",
316
+ prompt: "Help me de vragen en structuur voor een nieuwe survey te plannen voordat we die opstellen."
317
+ },
272
318
  review: {
273
319
  label: "Survey controleren",
274
320
  description: "Problemen vinden en verbeteringen voorstellen",
275
- prompt: "Controleer de huidige survey en stel verbeteringen voor."
321
+ prompt: "Voer een deterministische controle van de huidige survey uit, inclusief de geldigheid van expressieverwijzingen, beoordeel daarna de kwaliteit en stel verbeteringen voor."
276
322
  },
277
323
  create: {
278
324
  label: "Nieuwe vraag",
@@ -283,6 +329,11 @@ const surveyAssistantNlMessages = {
283
329
  label: "Formulering verbeteren",
284
330
  description: "Tekst voor respondenten aanscherpen",
285
331
  prompt: "Stel verbeteringen voor voor de tekst die respondenten zien."
332
+ },
333
+ refineSelection: {
334
+ label: "Geselecteerd item verbeteren",
335
+ description: "Scherp de formulering in context aan",
336
+ prompt: "Stel verbeteringen voor voor de respondentgerichte formulering van het geselecteerde item."
286
337
  }
287
338
  },
288
339
  input: {
@@ -297,6 +348,8 @@ const surveyAssistantNlMessages = {
297
348
  defaultModel: "Standaardmodel"
298
349
  },
299
350
  conversation: {
351
+ loadEarlier: "Eerdere berichten laden",
352
+ loadingEarlier: "Eerdere berichten laden…",
300
353
  noMessages: "Nog geen berichten",
301
354
  startConversation: "Start een gesprek om hier berichten te zien",
302
355
  scrollToLatest: "Naar het nieuwste bericht scrollen",
@@ -432,6 +485,11 @@ const surveyAssistantNlMessages = {
432
485
  newChat: "Nieuwe chat",
433
486
  history: "Chatgeschiedenis",
434
487
  recent: "Recente chats",
488
+ search: "Chats zoeken",
489
+ searching: "Oudere chats zoeken…",
490
+ noMatches: "Geen overeenkomende chats",
491
+ loadOlder: "Oudere chats laden",
492
+ loadingOlder: "Oudere chats laden…",
435
493
  automaticTitle: "Automatische titel gebruiken",
436
494
  automaticShort: "Automatisch",
437
495
  rename: "{title} hernoemen",
@@ -708,7 +766,7 @@ const Message = ({ className, from, ...props }) => /* @__PURE__ */ jsx("div", {
708
766
  ...props
709
767
  });
710
768
  const MessageContent = ({ children, className, ...props }) => /* @__PURE__ */ jsx("div", {
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 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),
769
+ 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),
712
770
  ...props,
713
771
  children
714
772
  });
@@ -739,13 +797,18 @@ const usePromptInput = () => {
739
797
  if (!context) throw new Error("PromptInput components must be used within PromptInput.");
740
798
  return context;
741
799
  };
742
- const PromptInput = ({ attachmentsDisabled = false, children, className, onDragEnter, onDragLeave, onDragOver, onDrop, onPaste, onSubmit, ...props }) => {
743
- const [value, setValue] = useState("");
800
+ const PromptInput = ({ attachmentsDisabled = false, children, className, onDragEnter, onDragLeave, onDragOver, onDrop, onPaste, onSubmit, onValueChange, value: controlledValue, ...props }) => {
801
+ const [uncontrolledValue, setUncontrolledValue] = useState("");
744
802
  const [files, setFiles] = useState([]);
745
803
  const [isDraggingFiles, setIsDraggingFiles] = useState(false);
746
804
  const [unsupportedFileNames, setUnsupportedFileNames] = useState([]);
747
805
  const fileDragDepthRef = useRef(0);
748
806
  const t = useSurveyAssistantTranslation();
807
+ const value = controlledValue ?? uncontrolledValue;
808
+ const setValue = useCallback((nextValue) => {
809
+ if (controlledValue === void 0) setUncontrolledValue(nextValue);
810
+ onValueChange?.(nextValue);
811
+ }, [controlledValue, onValueChange]);
749
812
  const addFiles = useCallback((fileList) => {
750
813
  const nextFiles = Array.from(fileList ?? []);
751
814
  const supportedFiles = nextFiles.filter(isSupportedFile);
@@ -947,15 +1010,6 @@ const PromptInputSubmit = ({ className, status, onStop, onClick, ...props }) =>
947
1010
  });
948
1011
  };
949
1012
  //#endregion
950
- //#region src/ui/internal/tool-technical-details.ts
951
- const formatOmittedLength = (length) => length.toLocaleString("en-US");
952
- const stringifyToolTechnicalDetails = (value) => JSON.stringify(value, function redactEmbeddedBinary(key, currentValue) {
953
- const parent = this && typeof this === "object" ? this : void 0;
954
- if (key === "data" && typeof currentValue === "string" && parent?.type === "embedded" && parent.encoding === "base64") return `[embedded base64 omitted: ${formatOmittedLength(currentValue.length)} characters]`;
955
- if (typeof currentValue === "string" && currentValue.length > 512 && /^data:[^;,]+;base64,/.test(currentValue)) return `[base64 data URL omitted: ${formatOmittedLength(currentValue.length)} characters]`;
956
- return currentValue;
957
- }, 2);
958
- //#endregion
959
1013
  //#region src/ui/internal/select.tsx
960
1014
  function Select$1({ ...props }) {
961
1015
  return /* @__PURE__ */ jsx(Select.Root, {
@@ -1030,6 +1084,88 @@ function SelectScrollDownButton({ className, ...props }) {
1030
1084
  });
1031
1085
  }
1032
1086
  //#endregion
1087
+ //#region src/ui/survey-assistant-prompt-input.tsx
1088
+ const SurveyAssistantModelSelect = ({ disabled = false, modelId, models, onModelChange }) => {
1089
+ const t = useSurveyAssistantTranslation();
1090
+ const selectedModelLabel = models.find((model) => model.id === modelId)?.label ?? modelId ?? t("input.defaultModel");
1091
+ if (models.length > 1) return /* @__PURE__ */ jsxs(Select$1, {
1092
+ disabled,
1093
+ onValueChange: onModelChange,
1094
+ value: modelId ?? "",
1095
+ children: [/* @__PURE__ */ jsxs(SelectTrigger, {
1096
+ "aria-label": t("input.model"),
1097
+ 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",
1098
+ size: "sm",
1099
+ title: modelId,
1100
+ children: [/* @__PURE__ */ jsx(BotIcon, { className: "size-3.5" }), /* @__PURE__ */ jsx(SelectValue, {})]
1101
+ }), /* @__PURE__ */ jsx(SelectContent, {
1102
+ align: "end",
1103
+ className: "min-w-52 rounded-lg border-border p-0 shadow-lg",
1104
+ position: "popper",
1105
+ children: models.map((model) => /* @__PURE__ */ jsx(SelectItem, {
1106
+ className: "min-h-9 rounded-md px-2 py-2 text-xs focus:bg-muted",
1107
+ title: model.id,
1108
+ value: model.id,
1109
+ children: model.label
1110
+ }, model.id))
1111
+ })]
1112
+ });
1113
+ return /* @__PURE__ */ jsxs("div", {
1114
+ className: "flex h-8 min-w-0 max-w-48 items-center gap-1.5 px-2 text-muted-foreground text-xs",
1115
+ title: modelId,
1116
+ children: [/* @__PURE__ */ jsx(BotIcon, { className: "size-3.5 shrink-0" }), /* @__PURE__ */ jsx("span", {
1117
+ className: "truncate",
1118
+ children: selectedModelLabel
1119
+ })]
1120
+ });
1121
+ };
1122
+ const SurveyAssistantPromptInput = ({ attachmentsDisabled = false, attachmentButtonClassName, className, disabled = false, footerClassName, formProps, modelDisabled = false, modelId, models, onModelChange, onStop, onSubmit, onValueChange, placeholder, status, textareaClassName, textareaProps, textareaRef, value }) => {
1123
+ const t = useSurveyAssistantTranslation();
1124
+ const canStop = Boolean(onStop) && (status === "submitted" || status === "streaming");
1125
+ return /* @__PURE__ */ jsxs(PromptInput, {
1126
+ ...formProps,
1127
+ attachmentsDisabled: attachmentsDisabled || disabled,
1128
+ className: cn("rounded-xl border-border bg-background shadow-sm", className),
1129
+ onSubmit,
1130
+ onValueChange,
1131
+ value,
1132
+ children: [/* @__PURE__ */ jsx(PromptInputBody, { children: /* @__PURE__ */ jsx(PromptInputTextarea, {
1133
+ ...textareaProps,
1134
+ className: cn("min-h-20 px-3 py-2 disabled:cursor-not-allowed disabled:text-muted-foreground", textareaClassName),
1135
+ disabled,
1136
+ placeholder: placeholder ?? t("input.placeholder"),
1137
+ ref: textareaRef
1138
+ }) }), /* @__PURE__ */ jsxs(PromptInputFooter, {
1139
+ className: cn("justify-between border-0 px-1.5 pt-0 pb-1.5", footerClassName),
1140
+ children: [/* @__PURE__ */ jsx(PromptInputAttachmentButton, {
1141
+ className: attachmentButtonClassName,
1142
+ disabled: attachmentsDisabled || disabled
1143
+ }), /* @__PURE__ */ jsxs("div", {
1144
+ className: "flex min-w-0 items-center justify-end gap-1",
1145
+ children: [/* @__PURE__ */ jsx(SurveyAssistantModelSelect, {
1146
+ disabled: disabled || modelDisabled,
1147
+ modelId,
1148
+ models,
1149
+ onModelChange
1150
+ }), /* @__PURE__ */ jsx(PromptInputSubmit, {
1151
+ disabled: disabled && !canStop,
1152
+ onStop,
1153
+ status
1154
+ })]
1155
+ })]
1156
+ })]
1157
+ });
1158
+ };
1159
+ //#endregion
1160
+ //#region src/ui/internal/tool-technical-details.ts
1161
+ const formatOmittedLength = (length) => length.toLocaleString("en-US");
1162
+ const stringifyToolTechnicalDetails = (value) => JSON.stringify(value, function redactEmbeddedBinary(key, currentValue) {
1163
+ const parent = this && typeof this === "object" ? this : void 0;
1164
+ if (key === "data" && typeof currentValue === "string" && parent?.type === "embedded" && parent.encoding === "base64") return `[embedded base64 omitted: ${formatOmittedLength(currentValue.length)} characters]`;
1165
+ if (typeof currentValue === "string" && currentValue.length > 512 && /^data:[^;,]+;base64,/.test(currentValue)) return `[base64 data URL omitted: ${formatOmittedLength(currentValue.length)} characters]`;
1166
+ return currentValue;
1167
+ }, 2);
1168
+ //#endregion
1033
1169
  //#region src/ui/internal/code-block.tsx
1034
1170
  const isItalic = (fontStyle) => fontStyle && fontStyle & 1;
1035
1171
  const isBold = (fontStyle) => fontStyle && fontStyle & 2;
@@ -1273,26 +1409,61 @@ const getPersistedMessageTurnStatus = (message) => {
1273
1409
  };
1274
1410
  //#endregion
1275
1411
  //#region src/ui/survey-assistant-panel.tsx
1276
- const getAssistantQuickPrompts = (t) => [
1277
- {
1278
- label: t("quickActions.review.label"),
1279
- description: t("quickActions.review.description"),
1280
- icon: ListChecksIcon,
1281
- prompt: t("quickActions.review.prompt")
1282
- },
1283
- {
1284
- label: t("quickActions.create.label"),
1285
- description: t("quickActions.create.description"),
1286
- icon: MessageSquarePlusIcon,
1287
- prompt: t("quickActions.create.prompt")
1288
- },
1289
- {
1290
- label: t("quickActions.refine.label"),
1291
- description: t("quickActions.refine.description"),
1292
- icon: WandSparklesIcon,
1293
- prompt: t("quickActions.refine.prompt")
1412
+ const isRootSurveyItem = (item) => typeof item.config === "object" && item.config !== null && item.config.isRoot === true;
1413
+ const getAssistantEmptyStateContext = (controller) => {
1414
+ try {
1415
+ const snapshot = controller?.getSnapshot();
1416
+ if (!snapshot) return "survey";
1417
+ const surveyItems = snapshot.survey.surveyItems.filter((item) => !isRootSurveyItem(item));
1418
+ if (surveyItems.length === 0) return "new-survey";
1419
+ if (surveyItems.some((item) => item.id === snapshot.ui.selectedItemId)) return "selected-item";
1420
+ return "survey";
1421
+ } catch {
1422
+ return "survey";
1294
1423
  }
1295
- ];
1424
+ };
1425
+ const getAssistantQuickPrompts = (t, context) => {
1426
+ if (context === "new-survey") return [
1427
+ {
1428
+ label: t("quickActions.draftSurvey.label"),
1429
+ description: t("quickActions.draftSurvey.description"),
1430
+ icon: WandSparklesIcon,
1431
+ prompt: t("quickActions.draftSurvey.prompt")
1432
+ },
1433
+ {
1434
+ label: t("quickActions.useBrief.label"),
1435
+ description: t("quickActions.useBrief.description"),
1436
+ icon: FileIcon,
1437
+ prompt: t("quickActions.useBrief.prompt")
1438
+ },
1439
+ {
1440
+ label: t("quickActions.planSurvey.label"),
1441
+ description: t("quickActions.planSurvey.description"),
1442
+ icon: ListChecksIcon,
1443
+ prompt: t("quickActions.planSurvey.prompt")
1444
+ }
1445
+ ];
1446
+ return [
1447
+ {
1448
+ label: t("quickActions.review.label"),
1449
+ description: t("quickActions.review.description"),
1450
+ icon: ListChecksIcon,
1451
+ prompt: t("quickActions.review.prompt")
1452
+ },
1453
+ {
1454
+ label: t("quickActions.create.label"),
1455
+ description: t("quickActions.create.description"),
1456
+ icon: MessageSquarePlusIcon,
1457
+ prompt: t("quickActions.create.prompt")
1458
+ },
1459
+ {
1460
+ label: t(context === "selected-item" ? "quickActions.refineSelection.label" : "quickActions.refine.label"),
1461
+ description: t(context === "selected-item" ? "quickActions.refineSelection.description" : "quickActions.refine.description"),
1462
+ icon: WandSparklesIcon,
1463
+ prompt: t(context === "selected-item" ? "quickActions.refineSelection.prompt" : "quickActions.refine.prompt")
1464
+ }
1465
+ ];
1466
+ };
1296
1467
  const isSurveyAssistantAttachmentReferencePart = (part) => {
1297
1468
  if (part.type !== "data-survey-assistant-attachment" || typeof part.data !== "object" || part.data === null) return false;
1298
1469
  const data = part.data;
@@ -1321,6 +1492,7 @@ const getAssistantErrorPresentation = (error, t) => {
1321
1492
  title: t(`errors.${parsed.code}.title`),
1322
1493
  description: parsed.detail ?? t("errors.fallbackDetail"),
1323
1494
  hint: t(`errors.${parsed.code}.hint`),
1495
+ retryable: parsed.retryable ?? (parsed.code === "rate_limited" || parsed.code === "request_failed"),
1324
1496
  tone: parsed.code === "model_unavailable" || parsed.code === "rate_limited" ? "warning" : "error"
1325
1497
  };
1326
1498
  };
@@ -1349,7 +1521,7 @@ const SurveyAssistantToolOutputSummary = ({ applyResult, output }) => {
1349
1521
  const statusLabel = status === "applied" ? t("changes.applied") : status === "stale" ? t("changes.stale") : status === "failed" ? t("changes.failed") : isValid ? t("changes.prepared") : t("changes.rejected");
1350
1522
  const statusMessage = applyResult?.message ?? unwrappedOutput.summary;
1351
1523
  return /* @__PURE__ */ jsxs("div", {
1352
- 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"),
1524
+ 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"),
1353
1525
  children: [
1354
1526
  /* @__PURE__ */ jsxs("div", {
1355
1527
  className: "flex items-center gap-2 font-medium",
@@ -1447,11 +1619,20 @@ const SurveyAssistantToolStep = ({ applyResultsByChangeId, terminalTurnStatus, p
1447
1619
  const errorText = getToolErrorText(part);
1448
1620
  const changeSet = output === void 0 ? null : getChangeSet(output);
1449
1621
  const applyResult = changeSet ? applyResultsByChangeId[changeSet.id] : void 0;
1622
+ const changeHasError = changeSet?.status === "rejected" || applyResult?.status === "rejected" || applyResult?.status === "failed";
1450
1623
  const title = changeSet ? toolName === "survey_expression" ? t("activity.expressionChange") : t("activity.surveyChange") : getToolTitle(t, toolName, part.state);
1451
1624
  const meta = terminalTurnStatus && toolStatusMeta[part.state].stepStatus === "active" ? terminalTurnStatus === "aborted" ? getStoppedToolStatusMeta(t) : {
1452
1625
  icon: /* @__PURE__ */ jsx(XCircleIcon, { className: "size-3 text-red-600" }),
1453
1626
  label: t("activity.failed"),
1454
1627
  stepStatus: "complete"
1628
+ } : changeHasError ? {
1629
+ icon: /* @__PURE__ */ jsx(XCircleIcon, { className: "size-3 text-red-900" }),
1630
+ label: null,
1631
+ stepStatus: "complete"
1632
+ } : applyResult?.status === "stale" ? {
1633
+ icon: /* @__PURE__ */ jsx(ClockIcon, { className: "size-3 text-amber-600" }),
1634
+ label: null,
1635
+ stepStatus: "complete"
1455
1636
  } : toolStatusMeta[part.state];
1456
1637
  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"));
1457
1638
  const [detailsOpen, setDetailsOpen] = useState(part.state === "output-error");
@@ -1483,16 +1664,16 @@ const SurveyAssistantToolStep = ({ applyResultsByChangeId, terminalTurnStatus, p
1483
1664
  onOpenChange: setDetailsOpen,
1484
1665
  open: detailsOpen,
1485
1666
  children: [/* @__PURE__ */ jsxs(CollapsibleTrigger, {
1486
- className: "flex items-center gap-1 text-muted-foreground text-xs hover:text-foreground",
1667
+ 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",
1487
1668
  children: [/* @__PURE__ */ jsx(ChevronRightIcon, { className: cn("size-3 transition-transform", detailsOpen && "rotate-90") }), t(detailsOpen ? "activity.hideTechnicalDetails" : "activity.technicalDetails")]
1488
1669
  }), /* @__PURE__ */ jsxs(CollapsibleContent, {
1489
- className: "mt-2 space-y-2",
1670
+ className: "mt-1.5 space-y-1.5",
1490
1671
  children: [part.input !== void 0 && /* @__PURE__ */ jsx(CodeBlock, {
1491
- className: "border-border/50 bg-muted/30",
1672
+ className: "border-border/60 bg-background/70 [&_code]:text-xs [&_pre]:p-2.5 [&_pre]:text-xs",
1492
1673
  code: stringifyToolTechnicalDetails(part.input),
1493
1674
  language: "json"
1494
1675
  }), (output !== void 0 || errorText) && /* @__PURE__ */ jsx(CodeBlock, {
1495
- className: cn("bg-muted/30", errorText ? "border-destructive/30" : "border-border/50"),
1676
+ className: cn("bg-background/70 [&_code]:text-xs [&_pre]:p-2.5 [&_pre]:text-xs", errorText ? "border-destructive/25" : "border-border/60"),
1496
1677
  code: errorText ?? stringifyToolTechnicalDetails(output),
1497
1678
  language: "json"
1498
1679
  })]
@@ -1625,7 +1806,7 @@ const SurveyAssistantMessageParts = ({ applyResultsByChangeId, isLastMessage, is
1625
1806
  const isChainActive = isLastMessage && isStreaming && isLastBlock;
1626
1807
  return /* @__PURE__ */ jsxs(ChainOfThought, {
1627
1808
  autoClose: true,
1628
- className: "w-full rounded-md bg-muted/35 px-2 py-1.5",
1809
+ className: "w-full rounded-md bg-muted/60 px-2 py-1.5",
1629
1810
  defaultDuration: persistedDuration,
1630
1811
  defaultOpen: isChainActive,
1631
1812
  duration: isChainActive ? void 0 : persistedDuration,
@@ -1721,10 +1902,11 @@ const SurveyAssistantHeaderIdentity = ({ indicatorClassName, status, title }) =>
1721
1902
  })]
1722
1903
  })]
1723
1904
  });
1724
- const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = false, initialLaunch, initialMessages, modelId, models, onInitialLaunchHandled, onModelChange, onFirstSubmit, onTurnSettled, onApplyReceipts, onSettleApply, onReviewChangeSets, onTurnActiveChange, resourceId, surveyId, threadControls, threadId, threadTitle }) => {
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 }) => {
1725
1906
  const t = useSurveyAssistantTranslation();
1726
- const assistantQuickPrompts = getAssistantQuickPrompts(t);
1727
- const { applyResultsByChangeId, authorizationError, dismissError, error, isStreaming, latestTurnOutcome, messages, state, status, stop, submit, retry } = useSurveyAssistantSession({
1907
+ const emptyStateContext = getAssistantEmptyStateContext(controller);
1908
+ const assistantQuickPrompts = getAssistantQuickPrompts(t, emptyStateContext);
1909
+ const { applyResultsByChangeId, authorizationError, dismissError, error, isStreaming, latestTurnOutcome, messages, prependMessages, state, status, stop, submit, retry } = useSurveyAssistantSession({
1728
1910
  api,
1729
1911
  applyPolicy,
1730
1912
  controller,
@@ -1741,7 +1923,6 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1741
1923
  const hasSubmittedMessageRef = useRef((initialMessages?.length ?? 0) > 0);
1742
1924
  const showAssistantError = Boolean(authorizationError) || Boolean(error);
1743
1925
  const assistantErrorPresentation = getAssistantErrorPresentation(authorizationError ?? error, t);
1744
- const selectedModelLabel = models.find((model) => model.id === modelId)?.label ?? modelId ?? t("input.defaultModel");
1745
1926
  const lastMessage = messages.at(-1);
1746
1927
  const isAssistantTurnActive = isSurveyAssistantTurnActive({
1747
1928
  isStreaming,
@@ -1750,6 +1931,20 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1750
1931
  const promptTextareaRef = useRef(null);
1751
1932
  const shouldRestorePromptFocusRef = useRef(false);
1752
1933
  const turnWasActiveRef = useRef(false);
1934
+ const [loadingEarlierMessages, setLoadingEarlierMessages] = useState(false);
1935
+ const loadEarlierMessages = useCallback(async () => {
1936
+ if (loadingEarlierMessages) return;
1937
+ setLoadingEarlierMessages(true);
1938
+ try {
1939
+ prependMessages(await onLoadEarlierMessages());
1940
+ } finally {
1941
+ setLoadingEarlierMessages(false);
1942
+ }
1943
+ }, [
1944
+ loadingEarlierMessages,
1945
+ onLoadEarlierMessages,
1946
+ prependMessages
1947
+ ]);
1753
1948
  const isAssistantTurnSettled = !isStreaming && (state === "completed" || state === "failed" || state === "aborted" || state === "applied");
1754
1949
  useEffect(() => {
1755
1950
  if (!isAssistantTurnActive) {
@@ -1815,8 +2010,10 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1815
2010
  }), [handleSubmit]);
1816
2011
  useEffect(() => {
1817
2012
  if (!initialLaunch) return;
2013
+ if (initialLaunch.modelId) onModelChange(initialLaunch.modelId);
1818
2014
  if (submit({
1819
2015
  files: initialLaunch.files ?? [],
2016
+ modelId: initialLaunch.modelId,
1820
2017
  text: initialLaunch.prompt,
1821
2018
  uiContext: initialLaunch.uiContext
1822
2019
  })) {
@@ -1830,6 +2027,7 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1830
2027
  initialLaunch,
1831
2028
  onFirstSubmit,
1832
2029
  onInitialLaunchHandled,
2030
+ onModelChange,
1833
2031
  submit
1834
2032
  ]);
1835
2033
  return /* @__PURE__ */ jsxs("aside", {
@@ -1837,7 +2035,7 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1837
2035
  className: cn("flex h-full min-h-0 flex-col overflow-visible bg-background", !embedded && "rounded-xl border border-border shadow-sm"),
1838
2036
  children: [
1839
2037
  /* @__PURE__ */ jsxs("header", {
1840
- className: "box-content flex h-9 shrink-0 items-center justify-between gap-2 border-b border-border bg-muted/20 px-2.5",
2038
+ className: "box-content flex h-9 shrink-0 items-center justify-between gap-2 border-b border-border bg-background px-2.5",
1841
2039
  children: [/* @__PURE__ */ jsx(SurveyAssistantHeaderIdentity, {
1842
2040
  indicatorClassName: showAssistantError || state === "failed" ? "bg-destructive" : isAssistantTurnActive ? "animate-pulse bg-blue-500" : state === "aborted" ? "bg-muted-foreground" : "bg-emerald-500",
1843
2041
  status: sessionStatusLabel,
@@ -1845,7 +2043,7 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1845
2043
  }), threadControls]
1846
2044
  }),
1847
2045
  /* @__PURE__ */ jsxs(Conversation, {
1848
- className: "min-h-0",
2046
+ className: "min-h-0 bg-(--surface-bg-color)",
1849
2047
  children: [/* @__PURE__ */ jsx(ConversationContent, {
1850
2048
  className: "gap-3 px-3 py-2.5",
1851
2049
  children: messages.length === 0 ? /* @__PURE__ */ jsx(ConversationEmptyState, {
@@ -1854,21 +2052,21 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1854
2052
  className: "mx-auto w-full max-w-sm",
1855
2053
  children: [
1856
2054
  /* @__PURE__ */ jsx("div", {
1857
- className: "mb-3 flex size-10 items-center justify-center rounded-xl bg-foreground text-background shadow-sm",
1858
- children: /* @__PURE__ */ jsx(SparklesIcon, { className: "size-5" })
2055
+ className: "mb-3 flex size-9 items-center justify-center rounded-xl border border-border bg-muted text-foreground shadow-xs",
2056
+ children: /* @__PURE__ */ jsx(SparklesIcon, { className: "size-4" })
1859
2057
  }),
1860
2058
  /* @__PURE__ */ jsx("h3", {
1861
2059
  className: "font-semibold text-foreground text-base tracking-tight",
1862
- children: t("empty.title")
2060
+ children: t(emptyStateContext === "new-survey" ? "empty.newSurveyTitle" : "empty.title")
1863
2061
  }),
1864
2062
  /* @__PURE__ */ jsx("p", {
1865
2063
  className: "mt-1 text-muted-foreground text-xs leading-relaxed",
1866
- children: t("empty.description")
2064
+ children: t(emptyStateContext === "new-survey" ? "empty.newSurveyDescription" : "empty.description")
1867
2065
  }),
1868
2066
  /* @__PURE__ */ jsx("div", {
1869
2067
  className: "mt-4 flex flex-col gap-1.5",
1870
2068
  children: assistantQuickPrompts.map(({ label, description, icon: Icon, prompt }) => /* @__PURE__ */ jsxs("button", {
1871
- 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",
2069
+ 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",
1872
2070
  onClick: () => handleQuickPrompt(prompt),
1873
2071
  type: "button",
1874
2072
  children: [
@@ -1892,40 +2090,50 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1892
2090
  })
1893
2091
  ]
1894
2092
  })
1895
- }) : /* @__PURE__ */ jsxs(Fragment, { children: [messages.map((message, index) => {
1896
- const isLastMessage = index === messages.length - 1;
1897
- const persistedTurnStatus = getPersistedMessageTurnStatus(message);
1898
- const terminalTurnStatus = persistedTurnStatus === "aborted" || persistedTurnStatus === "failed" ? persistedTurnStatus : isLastMessage && (state === "aborted" || state === "failed") ? state : void 0;
1899
- const shouldShowProgress = isAssistantTurnActive && isLastMessage && message.role === "assistant";
1900
- return /* @__PURE__ */ jsx(Message, {
2093
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2094
+ hasEarlierMessages && /* @__PURE__ */ jsxs("button", {
2095
+ className: "mx-auto flex h-8 items-center justify-center rounded-md px-3 text-muted-foreground text-xs hover:bg-muted hover:text-foreground disabled:pointer-events-none",
2096
+ disabled: loadingEarlierMessages || isAssistantTurnActive,
2097
+ onClick: () => void loadEarlierMessages(),
2098
+ type: "button",
2099
+ children: [loadingEarlierMessages && /* @__PURE__ */ jsx(LoaderCircleIcon, { className: "mr-1.5 size-3.5 animate-spin" }), loadingEarlierMessages ? t("conversation.loadingEarlier") : t("conversation.loadEarlier")]
2100
+ }),
2101
+ messages.map((message, index) => {
2102
+ const isLastMessage = index === messages.length - 1;
2103
+ const persistedTurnStatus = getPersistedMessageTurnStatus(message);
2104
+ const terminalTurnStatus = persistedTurnStatus === "aborted" || persistedTurnStatus === "failed" ? persistedTurnStatus : isLastMessage && (state === "aborted" || state === "failed") ? state : void 0;
2105
+ const shouldShowProgress = isAssistantTurnActive && isLastMessage && message.role === "assistant";
2106
+ return /* @__PURE__ */ jsx(Message, {
2107
+ className: "max-w-[96%] gap-1",
2108
+ from: message.role,
2109
+ children: /* @__PURE__ */ jsx(MessageContent, {
2110
+ className: cn(message.role === "assistant" && "w-full space-y-2"),
2111
+ children: hasRenderableParts(message) ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(SurveyAssistantMessageParts, {
2112
+ applyResultsByChangeId,
2113
+ isLastMessage,
2114
+ isStreaming: isAssistantTurnActive,
2115
+ message,
2116
+ showProgress: shouldShowProgress,
2117
+ terminalTurnStatus
2118
+ }), isLastMessage && latestTurnOutcome && /* @__PURE__ */ jsx(SurveyAssistantTurnOutcomeNotice, { ...latestTurnOutcome })] }) : shouldShowProgress ? /* @__PURE__ */ jsx(SurveyAssistantProgressIndicator, { message }) : isLastMessage && latestTurnOutcome ? /* @__PURE__ */ jsx(SurveyAssistantTurnOutcomeNotice, { ...latestTurnOutcome }) : state === "aborted" ? /* @__PURE__ */ jsx("span", {
2119
+ className: "text-muted-foreground text-sm",
2120
+ children: t("conversation.stopped")
2121
+ }) : /* @__PURE__ */ jsx("span", {
2122
+ className: "text-muted-foreground text-sm",
2123
+ children: t("conversation.incomplete")
2124
+ })
2125
+ })
2126
+ }, message.id);
2127
+ }),
2128
+ showPendingAssistantRow && /* @__PURE__ */ jsx(Message, {
1901
2129
  className: "max-w-[96%] gap-1",
1902
- from: message.role,
2130
+ from: "assistant",
1903
2131
  children: /* @__PURE__ */ jsx(MessageContent, {
1904
- className: cn(message.role === "assistant" && "w-full space-y-2"),
1905
- children: hasRenderableParts(message) ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(SurveyAssistantMessageParts, {
1906
- applyResultsByChangeId,
1907
- isLastMessage,
1908
- isStreaming: isAssistantTurnActive,
1909
- message,
1910
- showProgress: shouldShowProgress,
1911
- terminalTurnStatus
1912
- }), isLastMessage && latestTurnOutcome && /* @__PURE__ */ jsx(SurveyAssistantTurnOutcomeNotice, { ...latestTurnOutcome })] }) : shouldShowProgress ? /* @__PURE__ */ jsx(SurveyAssistantProgressIndicator, { message }) : isLastMessage && latestTurnOutcome ? /* @__PURE__ */ jsx(SurveyAssistantTurnOutcomeNotice, { ...latestTurnOutcome }) : state === "aborted" ? /* @__PURE__ */ jsx("span", {
1913
- className: "text-muted-foreground text-sm",
1914
- children: t("conversation.stopped")
1915
- }) : /* @__PURE__ */ jsx("span", {
1916
- className: "text-muted-foreground text-sm",
1917
- children: t("conversation.incomplete")
1918
- })
2132
+ className: "w-full",
2133
+ children: /* @__PURE__ */ jsx(SurveyAssistantProgressIndicator, {})
1919
2134
  })
1920
- }, message.id);
1921
- }), showPendingAssistantRow && /* @__PURE__ */ jsx(Message, {
1922
- className: "max-w-[96%] gap-1",
1923
- from: "assistant",
1924
- children: /* @__PURE__ */ jsx(MessageContent, {
1925
- className: "w-full",
1926
- children: /* @__PURE__ */ jsx(SurveyAssistantProgressIndicator, {})
1927
2135
  })
1928
- })] })
2136
+ ] })
1929
2137
  }), /* @__PURE__ */ jsx(ConversationScrollButton, { className: "bottom-2.5" })]
1930
2138
  }),
1931
2139
  showAssistantError && /* @__PURE__ */ jsxs("div", {
@@ -1948,7 +2156,7 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1948
2156
  className: "mt-1 text-muted-foreground/80",
1949
2157
  children: assistantErrorPresentation.hint
1950
2158
  }),
1951
- /* @__PURE__ */ jsx("button", {
2159
+ assistantErrorPresentation.retryable && /* @__PURE__ */ jsx("button", {
1952
2160
  className: "mt-1.5 font-medium text-foreground text-xs hover:underline",
1953
2161
  onClick: retry,
1954
2162
  type: "button",
@@ -1965,58 +2173,21 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1965
2173
  })]
1966
2174
  }),
1967
2175
  /* @__PURE__ */ jsx("div", {
1968
- className: "shrink-0 border-t border-border bg-muted/20 p-2",
1969
- children: /* @__PURE__ */ jsxs(PromptInput, {
1970
- "aria-busy": isAssistantTurnActive,
1971
- attachmentsDisabled: isAssistantTurnActive,
1972
- className: cn("rounded-xl border-border bg-background shadow-sm transition-colors", isAssistantTurnActive && "border-border/60 bg-muted/50 shadow-none"),
2176
+ className: "shrink-0 border-t border-border bg-background p-2",
2177
+ children: /* @__PURE__ */ jsx(SurveyAssistantPromptInput, {
2178
+ attachmentButtonClassName: "size-8",
2179
+ className: cn("rounded-xl border-border bg-popover shadow-sm transition-colors", isAssistantTurnActive && "border-border/60 bg-muted/50 shadow-none"),
2180
+ disabled: isAssistantTurnActive,
2181
+ footerClassName: cn("transition-colors", isAssistantTurnActive && "bg-muted/30"),
2182
+ formProps: { "aria-busy": isAssistantTurnActive },
2183
+ modelId,
2184
+ models,
2185
+ onModelChange,
2186
+ onStop: stop,
1973
2187
  onSubmit: handleSubmit,
1974
- children: [/* @__PURE__ */ jsx(PromptInputBody, { children: /* @__PURE__ */ jsx(PromptInputTextarea, {
1975
- 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",
1976
- disabled: isAssistantTurnActive,
1977
- placeholder: t("input.placeholder"),
1978
- ref: promptTextareaRef
1979
- }) }), /* @__PURE__ */ jsxs(PromptInputFooter, {
1980
- className: cn("justify-between border-0 px-1.5 pt-0 pb-1.5 transition-colors", isAssistantTurnActive && "bg-muted/30"),
1981
- children: [/* @__PURE__ */ jsx(PromptInputAttachmentButton, {
1982
- className: "size-8",
1983
- disabled: isAssistantTurnActive
1984
- }), /* @__PURE__ */ jsxs("div", {
1985
- className: "flex min-w-0 items-center justify-end gap-1",
1986
- children: [models.length > 1 && modelId ? /* @__PURE__ */ jsxs(Select$1, {
1987
- disabled: isAssistantTurnActive,
1988
- onValueChange: onModelChange,
1989
- value: modelId,
1990
- children: [/* @__PURE__ */ jsxs(SelectTrigger, {
1991
- "aria-label": t("input.model"),
1992
- 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",
1993
- size: "sm",
1994
- title: modelId,
1995
- children: [/* @__PURE__ */ jsx(BotIcon, { className: "size-3.5" }), /* @__PURE__ */ jsx(SelectValue, {})]
1996
- }), /* @__PURE__ */ jsx(SelectContent, {
1997
- align: "end",
1998
- className: "min-w-52 rounded-lg border-border p-0 shadow-lg",
1999
- position: "popper",
2000
- children: models.map((model) => /* @__PURE__ */ jsx(SelectItem, {
2001
- className: "min-h-9 rounded-md px-2 py-2 text-xs focus:bg-muted",
2002
- title: model.id,
2003
- value: model.id,
2004
- children: model.label
2005
- }, model.id))
2006
- })]
2007
- }) : /* @__PURE__ */ jsxs("div", {
2008
- className: "flex h-8 min-w-0 max-w-48 items-center gap-1.5 px-2 text-muted-foreground text-xs",
2009
- title: modelId,
2010
- children: [/* @__PURE__ */ jsx(BotIcon, { className: "size-3.5 shrink-0" }), /* @__PURE__ */ jsx("span", {
2011
- className: "truncate",
2012
- children: selectedModelLabel
2013
- })]
2014
- }), /* @__PURE__ */ jsx(PromptInputSubmit, {
2015
- onStop: stop,
2016
- status
2017
- })]
2018
- })]
2019
- })]
2188
+ status,
2189
+ textareaClassName: "min-h-11 max-h-28 px-3 pt-2.5 pb-1.5 transition-colors disabled:placeholder:text-muted-foreground/60",
2190
+ textareaRef: promptTextareaRef
2020
2191
  })
2021
2192
  })
2022
2193
  ]
@@ -2029,14 +2200,32 @@ const createThreadTitle = (text, fallback) => {
2029
2200
  const words = normalized.split(" ").slice(0, 8).join(" ");
2030
2201
  return words.length > 64 ? `${words.slice(0, 61).trimEnd()}...` : words;
2031
2202
  };
2203
+ const mergeThreads = (current, incoming) => {
2204
+ const byId = new Map(current.map((thread) => [thread.id, thread]));
2205
+ for (const thread of incoming) byId.set(thread.id, thread);
2206
+ return [...byId.values()].sort((left, right) => Date.parse(right.updatedAt) - Date.parse(left.updatedAt));
2207
+ };
2208
+ const mergeRecentThreadPage = (current, recent) => {
2209
+ if (recent.length === 0) return [];
2210
+ const oldestRecentUpdate = Math.min(...recent.map((thread) => Date.parse(thread.updatedAt)));
2211
+ return mergeThreads(Number.isFinite(oldestRecentUpdate) ? current.filter((thread) => Date.parse(thread.updatedAt) <= oldestRecentUpdate) : current, recent);
2212
+ };
2213
+ const listThreadPage = async (client, cursor) => {
2214
+ if (client.listPage) return client.listPage(cursor ? { cursor } : void 0);
2215
+ return cursor ? { threads: [] } : { threads: await client.list() };
2216
+ };
2032
2217
  const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], onConfirmDeleteThread, onModelChange = () => {}, onTurnActiveChange: onAssistantTurnActiveChange, resourceId, selectedThreadStorageKey, surveyId, threadApi, threadClient: providedThreadClient, threadLauncher, ...sessionProps }) => {
2033
2218
  const t = useSurveyAssistantTranslation();
2034
2219
  const threadClient = useMemo(() => providedThreadClient ?? createSurveyAssistantThreadClient(threadApi), [providedThreadClient, threadApi]);
2035
2220
  const storageKey = selectedThreadStorageKey === false ? null : selectedThreadStorageKey ?? `${SELECTED_THREAD_STORAGE_KEY}:${encodeURIComponent(resourceId)}${surveyId ? `:${encodeURIComponent(surveyId)}` : ""}`;
2036
2221
  const [threads, setThreads] = useState([]);
2222
+ const [nextThreadCursor, setNextThreadCursor] = useState();
2037
2223
  const [selectedThread, setSelectedThread] = useState(null);
2038
2224
  const [initialMessages, setInitialMessages] = useState([]);
2225
+ const [nextMessageCursor, setNextMessageCursor] = useState();
2039
2226
  const [historyOpen, setHistoryOpen] = useState(false);
2227
+ const [threadSearch, setThreadSearch] = useState("");
2228
+ const [loadingOlderThreads, setLoadingOlderThreads] = useState(false);
2040
2229
  const [editingThreadId, setEditingThreadId] = useState(null);
2041
2230
  const [editingTitle, setEditingTitle] = useState("");
2042
2231
  const [threadError, setThreadError] = useState(null);
@@ -2044,21 +2233,25 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2044
2233
  const [assistantTurnActive, setAssistantTurnActive] = useState(false);
2045
2234
  const [initialLaunch, setInitialLaunch] = useState();
2046
2235
  const historyControlsRef = useRef(null);
2236
+ const loadingAllThreadsRef = useRef(false);
2047
2237
  const selectModel = useCallback((modelId) => {
2048
2238
  if (!models.some((model) => model.id === modelId)) return;
2049
2239
  onModelChange(modelId);
2050
2240
  }, [models, onModelChange]);
2051
- const selectThread = useCallback(async (thread) => {
2241
+ const selectThread = useCallback(async (thread, reportError = true) => {
2052
2242
  setLoadingThread(true);
2053
2243
  setThreadError(null);
2054
2244
  try {
2055
2245
  const transcript = await threadClient.read(thread.id);
2056
2246
  setSelectedThread(transcript.thread);
2057
2247
  setInitialMessages(transcript.messages);
2248
+ setNextMessageCursor(transcript.nextCursor);
2058
2249
  setHistoryOpen(false);
2059
2250
  if (storageKey) window.localStorage.setItem(storageKey, thread.id);
2251
+ return transcript.thread;
2060
2252
  } catch (error) {
2061
- setThreadError(parseSurveyAssistantError(error).detail ?? t("errors.fallbackDetail"));
2253
+ if (reportError) setThreadError(parseSurveyAssistantError(error).detail ?? t("errors.fallbackDetail"));
2254
+ return null;
2062
2255
  } finally {
2063
2256
  setLoadingThread(false);
2064
2257
  }
@@ -2072,9 +2265,10 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2072
2265
  setThreadError(null);
2073
2266
  try {
2074
2267
  const thread = await threadClient.create(launch?.title);
2075
- setThreads((current) => [thread, ...current]);
2268
+ setThreads((current) => mergeThreads(current, [thread]));
2076
2269
  setSelectedThread(thread);
2077
2270
  setInitialMessages([]);
2271
+ setNextMessageCursor(void 0);
2078
2272
  setHistoryOpen(false);
2079
2273
  if (storageKey) window.localStorage.setItem(storageKey, thread.id);
2080
2274
  return thread;
@@ -2092,11 +2286,21 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2092
2286
  ]);
2093
2287
  useEffect(() => {
2094
2288
  let cancelled = false;
2095
- threadClient.list().then(async (availableThreads) => {
2289
+ listThreadPage(threadClient).then(async (page) => {
2096
2290
  if (cancelled) return;
2291
+ const availableThreads = page.threads;
2097
2292
  setThreads(availableThreads);
2293
+ setNextThreadCursor(page.nextCursor);
2098
2294
  const rememberedId = storageKey ? window.localStorage.getItem(storageKey) : null;
2099
- const initialThread = availableThreads.find((thread) => thread.id === rememberedId) ?? availableThreads[0];
2295
+ const rememberedThread = availableThreads.find((thread) => thread.id === rememberedId);
2296
+ if (rememberedId && !rememberedThread) {
2297
+ const restored = await selectThread({ id: rememberedId }, false);
2298
+ if (restored) {
2299
+ setThreads((current) => mergeThreads(current, [restored]));
2300
+ return;
2301
+ }
2302
+ }
2303
+ const initialThread = rememberedThread ?? availableThreads[0];
2100
2304
  if (initialThread) await selectThread(initialThread);
2101
2305
  else await createThread();
2102
2306
  }).catch((error) => {
@@ -2115,6 +2319,66 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2115
2319
  t,
2116
2320
  threadClient
2117
2321
  ]);
2322
+ const loadOlderThreads = useCallback(async () => {
2323
+ if (!nextThreadCursor || loadingOlderThreads) return;
2324
+ setLoadingOlderThreads(true);
2325
+ try {
2326
+ const page = await listThreadPage(threadClient, nextThreadCursor);
2327
+ setThreads((current) => mergeThreads(current, page.threads));
2328
+ setNextThreadCursor(page.nextCursor);
2329
+ } catch (error) {
2330
+ setThreadError(parseSurveyAssistantError(error).detail ?? t("errors.fallbackDetail"));
2331
+ } finally {
2332
+ setLoadingOlderThreads(false);
2333
+ }
2334
+ }, [
2335
+ loadingOlderThreads,
2336
+ nextThreadCursor,
2337
+ t,
2338
+ threadClient
2339
+ ]);
2340
+ const loadEarlierMessages = useCallback(async () => {
2341
+ if (!selectedThread || !nextMessageCursor) return [];
2342
+ try {
2343
+ const transcript = await threadClient.read(selectedThread.id, nextMessageCursor);
2344
+ setNextMessageCursor(transcript.nextCursor);
2345
+ return transcript.messages;
2346
+ } catch (error) {
2347
+ setThreadError(parseSurveyAssistantError(error).detail ?? t("errors.fallbackDetail"));
2348
+ return [];
2349
+ }
2350
+ }, [
2351
+ nextMessageCursor,
2352
+ selectedThread,
2353
+ t,
2354
+ threadClient
2355
+ ]);
2356
+ const loadAllThreadsForSearch = useCallback(async () => {
2357
+ if (!nextThreadCursor || loadingAllThreadsRef.current) return;
2358
+ loadingAllThreadsRef.current = true;
2359
+ setLoadingOlderThreads(true);
2360
+ try {
2361
+ let cursor = nextThreadCursor;
2362
+ while (cursor) {
2363
+ const page = await listThreadPage(threadClient, cursor);
2364
+ setThreads((current) => mergeThreads(current, page.threads));
2365
+ cursor = page.nextCursor;
2366
+ setNextThreadCursor(cursor);
2367
+ }
2368
+ } catch (error) {
2369
+ setThreadError(parseSurveyAssistantError(error).detail ?? t("errors.fallbackDetail"));
2370
+ } finally {
2371
+ loadingAllThreadsRef.current = false;
2372
+ setLoadingOlderThreads(false);
2373
+ }
2374
+ }, [
2375
+ nextThreadCursor,
2376
+ t,
2377
+ threadClient
2378
+ ]);
2379
+ useEffect(() => {
2380
+ if (threadSearch.trim()) loadAllThreadsForSearch();
2381
+ }, [loadAllThreadsForSearch, threadSearch]);
2118
2382
  useEffect(() => {
2119
2383
  if (!threadLauncher || loadingThread) return;
2120
2384
  return connectSurveyAssistantThreadLauncher(threadLauncher, async (launch) => {
@@ -2222,9 +2486,9 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2222
2486
  }, [initialLaunch]);
2223
2487
  const refreshThreadList = useCallback(async (reportError) => {
2224
2488
  try {
2225
- const availableThreads = await threadClient.list();
2226
- setThreads(availableThreads);
2227
- const current = availableThreads.find((thread) => thread.id === selectedThread?.id);
2489
+ const page = await listThreadPage(threadClient);
2490
+ setThreads((current) => mergeRecentThreadPage(current, page.threads));
2491
+ const current = page.threads.find((thread) => thread.id === selectedThread?.id);
2228
2492
  if (current && !editingThreadId) setSelectedThread((selected) => {
2229
2493
  if (!selected || selected.id !== current.id) return selected;
2230
2494
  return selected.title === current.title && selected.titleMode === current.titleMode && selected.updatedAt === current.updatedAt ? selected : current;
@@ -2289,7 +2553,7 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2289
2553
  if (loadingThread || !selectedThread) return /* @__PURE__ */ jsxs("aside", {
2290
2554
  className: cn("flex h-full min-h-0 flex-col overflow-visible bg-background", !embedded && "rounded-xl border border-border shadow-sm"),
2291
2555
  children: [/* @__PURE__ */ jsx("header", {
2292
- className: "box-content flex h-9 shrink-0 items-center border-b border-border bg-muted/20 px-2.5",
2556
+ className: "box-content flex h-9 shrink-0 items-center border-b border-border bg-background px-2.5",
2293
2557
  children: /* @__PURE__ */ jsx(SurveyAssistantHeaderIdentity, {
2294
2558
  indicatorClassName: "animate-pulse bg-blue-500",
2295
2559
  status: t("panel.loadingChats"),
@@ -2300,6 +2564,8 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2300
2564
  children: [/* @__PURE__ */ jsx(LoaderCircleIcon, { className: "mr-2 size-4 animate-spin" }), t("panel.loadingChats")]
2301
2565
  })]
2302
2566
  });
2567
+ const normalizedThreadSearch = threadSearch.trim().toLocaleLowerCase();
2568
+ const visibleThreads = normalizedThreadSearch ? threads.filter((thread) => thread.title.toLocaleLowerCase().includes(normalizedThreadSearch)) : threads;
2303
2569
  const threadControls = /* @__PURE__ */ jsxs("div", {
2304
2570
  className: "relative flex shrink-0 items-center gap-1",
2305
2571
  ref: historyControlsRef,
@@ -2325,65 +2591,92 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2325
2591
  }),
2326
2592
  historyOpen && /* @__PURE__ */ jsxs("div", {
2327
2593
  className: "absolute top-10 right-0 z-30 w-72 overflow-hidden rounded-lg border border-border bg-popover text-popover-foreground shadow-lg",
2328
- children: [/* @__PURE__ */ jsx("div", {
2329
- className: "border-b border-border px-3 py-2 font-medium text-xs",
2330
- children: t("threads.recent")
2331
- }), /* @__PURE__ */ jsx("div", {
2332
- className: "max-h-72 overflow-y-auto p-1",
2333
- children: threads.map((thread) => /* @__PURE__ */ jsxs("div", {
2334
- className: cn("group flex min-h-10 items-center gap-1 rounded-md px-2", thread.id === selectedThread.id ? "bg-muted" : "hover:bg-muted/60"),
2594
+ children: [
2595
+ /* @__PURE__ */ jsx("div", {
2596
+ className: "border-b border-border px-3 py-2 font-medium text-xs",
2597
+ children: t("threads.recent")
2598
+ }),
2599
+ /* @__PURE__ */ jsxs("div", {
2600
+ className: "relative border-b border-border p-2",
2601
+ children: [/* @__PURE__ */ jsx(SearchIcon, { className: "pointer-events-none absolute top-1/2 left-4 size-3.5 -translate-y-1/2 text-muted-foreground" }), /* @__PURE__ */ jsx("input", {
2602
+ "aria-label": t("threads.search"),
2603
+ className: "h-8 w-full rounded-md border border-input bg-background pr-2 pl-8 text-xs outline-none placeholder:text-muted-foreground focus:ring-1 focus:ring-ring",
2604
+ onChange: (event) => setThreadSearch(event.currentTarget.value),
2605
+ placeholder: t("threads.search"),
2606
+ type: "search",
2607
+ value: threadSearch
2608
+ })]
2609
+ }),
2610
+ /* @__PURE__ */ jsxs("div", {
2611
+ className: "max-h-72 overflow-y-auto p-1",
2335
2612
  children: [
2336
- editingThreadId === thread.id ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("input", {
2337
- autoFocus: true,
2338
- className: "min-w-0 flex-1 rounded border border-input bg-background px-1.5 py-1 text-xs outline-none focus:ring-1 focus:ring-ring",
2339
- onBlur: () => void commitEditingThread(),
2340
- onChange: (event) => setEditingTitle(event.currentTarget.value),
2341
- onKeyDown: (event) => {
2342
- if (event.key === "Enter") {
2343
- event.preventDefault();
2344
- commitEditingThread();
2345
- }
2346
- if (event.key === "Escape") {
2347
- event.preventDefault();
2348
- event.stopPropagation();
2349
- cancelEditingThread();
2350
- setHistoryOpen(false);
2351
- }
2352
- },
2353
- value: editingTitle
2354
- }), thread.titleMode === "manual" && /* @__PURE__ */ jsxs("button", {
2355
- "aria-label": t("threads.automaticTitle"),
2356
- className: "flex h-7 shrink-0 items-center gap-1 rounded px-1.5 text-muted-foreground text-xs hover:bg-background hover:text-foreground",
2357
- onClick: () => void setAutomaticTitle(thread),
2358
- onMouseDown: (event) => event.preventDefault(),
2359
- title: t("threads.automaticTitle"),
2360
- type: "button",
2361
- children: [/* @__PURE__ */ jsx(SparklesIcon, { className: "size-3" }), /* @__PURE__ */ jsx("span", { children: t("threads.automaticShort") })]
2362
- })] }) : /* @__PURE__ */ jsx("button", {
2363
- className: "min-w-0 flex-1 truncate py-2 text-left text-xs",
2364
- onClick: () => void selectThread(thread),
2365
- type: "button",
2366
- children: thread.title
2367
- }),
2368
- /* @__PURE__ */ jsx("button", {
2369
- "aria-label": t("threads.rename", { title: thread.title }),
2370
- className: "flex size-7 shrink-0 items-center justify-center rounded text-muted-foreground opacity-0 hover:bg-background hover:text-foreground group-hover:opacity-100",
2371
- onClick: () => beginEditingThread(thread),
2372
- title: t("threads.renameTitle"),
2373
- type: "button",
2374
- children: /* @__PURE__ */ jsx(PencilIcon, { className: "size-3.5" })
2613
+ visibleThreads.map((thread) => /* @__PURE__ */ jsxs("div", {
2614
+ className: cn("group flex min-h-10 items-center gap-1 rounded-md px-2", thread.id === selectedThread.id ? "bg-muted" : "hover:bg-muted/60"),
2615
+ children: [
2616
+ editingThreadId === thread.id ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("input", {
2617
+ autoFocus: true,
2618
+ className: "min-w-0 flex-1 rounded border border-input bg-background px-1.5 py-1 text-xs outline-none focus:ring-1 focus:ring-ring",
2619
+ onBlur: () => void commitEditingThread(),
2620
+ onChange: (event) => setEditingTitle(event.currentTarget.value),
2621
+ onKeyDown: (event) => {
2622
+ if (event.key === "Enter") {
2623
+ event.preventDefault();
2624
+ commitEditingThread();
2625
+ }
2626
+ if (event.key === "Escape") {
2627
+ event.preventDefault();
2628
+ event.stopPropagation();
2629
+ cancelEditingThread();
2630
+ setHistoryOpen(false);
2631
+ }
2632
+ },
2633
+ value: editingTitle
2634
+ }), thread.titleMode === "manual" && /* @__PURE__ */ jsxs("button", {
2635
+ "aria-label": t("threads.automaticTitle"),
2636
+ className: "flex h-7 shrink-0 items-center gap-1 rounded px-1.5 text-muted-foreground text-xs hover:bg-background hover:text-foreground",
2637
+ onClick: () => void setAutomaticTitle(thread),
2638
+ onMouseDown: (event) => event.preventDefault(),
2639
+ title: t("threads.automaticTitle"),
2640
+ type: "button",
2641
+ children: [/* @__PURE__ */ jsx(SparklesIcon, { className: "size-3" }), /* @__PURE__ */ jsx("span", { children: t("threads.automaticShort") })]
2642
+ })] }) : /* @__PURE__ */ jsx("button", {
2643
+ className: "min-w-0 flex-1 truncate py-2 text-left text-xs",
2644
+ onClick: () => void selectThread(thread),
2645
+ type: "button",
2646
+ children: thread.title
2647
+ }),
2648
+ /* @__PURE__ */ jsx("button", {
2649
+ "aria-label": t("threads.rename", { title: thread.title }),
2650
+ className: "flex size-7 shrink-0 items-center justify-center rounded text-muted-foreground opacity-0 hover:bg-background hover:text-foreground group-hover:opacity-100",
2651
+ onClick: () => beginEditingThread(thread),
2652
+ title: t("threads.renameTitle"),
2653
+ type: "button",
2654
+ children: /* @__PURE__ */ jsx(PencilIcon, { className: "size-3.5" })
2655
+ }),
2656
+ /* @__PURE__ */ jsx("button", {
2657
+ "aria-label": t("threads.delete", { title: thread.title }),
2658
+ className: "flex size-7 shrink-0 items-center justify-center rounded text-muted-foreground opacity-0 hover:bg-background hover:text-foreground group-hover:opacity-100",
2659
+ onClick: () => void deleteThread(thread),
2660
+ title: t("threads.deleteTitle"),
2661
+ type: "button",
2662
+ children: /* @__PURE__ */ jsx(Trash2Icon, { className: "size-3.5" })
2663
+ })
2664
+ ]
2665
+ }, thread.id)),
2666
+ visibleThreads.length === 0 && !loadingOlderThreads && /* @__PURE__ */ jsx("div", {
2667
+ className: "px-3 py-5 text-center text-muted-foreground text-xs",
2668
+ children: t("threads.noMatches")
2375
2669
  }),
2376
- /* @__PURE__ */ jsx("button", {
2377
- "aria-label": t("threads.delete", { title: thread.title }),
2378
- className: "flex size-7 shrink-0 items-center justify-center rounded text-muted-foreground opacity-0 hover:bg-background hover:text-foreground group-hover:opacity-100",
2379
- onClick: () => void deleteThread(thread),
2380
- title: t("threads.deleteTitle"),
2670
+ (nextThreadCursor || loadingOlderThreads) && /* @__PURE__ */ jsx("button", {
2671
+ className: "flex h-9 w-full items-center justify-center rounded-md text-muted-foreground text-xs hover:bg-muted hover:text-foreground disabled:pointer-events-none",
2672
+ disabled: loadingOlderThreads || Boolean(normalizedThreadSearch),
2673
+ onClick: () => void loadOlderThreads(),
2381
2674
  type: "button",
2382
- children: /* @__PURE__ */ jsx(Trash2Icon, { className: "size-3.5" })
2675
+ children: loadingOlderThreads ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(LoaderCircleIcon, { className: "mr-1.5 size-3.5 animate-spin" }), normalizedThreadSearch ? t("threads.searching") : t("threads.loadingOlder")] }) : t("threads.loadOlder")
2383
2676
  })
2384
2677
  ]
2385
- }, thread.id))
2386
- })]
2678
+ })
2679
+ ]
2387
2680
  })
2388
2681
  ]
2389
2682
  });
@@ -2392,6 +2685,7 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2392
2685
  children: [/* @__PURE__ */ createElement(SurveyAssistantChatPanel, {
2393
2686
  ...sessionProps,
2394
2687
  embedded,
2688
+ hasEarlierMessages: Boolean(nextMessageCursor),
2395
2689
  initialLaunch: initialLaunch?.input,
2396
2690
  initialMessages,
2397
2691
  key: selectedThread.id,
@@ -2400,6 +2694,7 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2400
2694
  onInitialLaunchHandled: handleInitialLaunchHandled,
2401
2695
  onModelChange: selectModel,
2402
2696
  onFirstSubmit: handleFirstSubmit,
2697
+ onLoadEarlierMessages: loadEarlierMessages,
2403
2698
  onTurnSettled: handleTurnSettled,
2404
2699
  onTurnActiveChange: handleTurnActiveChange,
2405
2700
  resourceId,
@@ -2408,7 +2703,7 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2408
2703
  threadId: selectedThread.id,
2409
2704
  threadTitle: selectedThread.title
2410
2705
  }), threadError && /* @__PURE__ */ jsxs("div", {
2411
- 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",
2706
+ 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",
2412
2707
  children: [
2413
2708
  /* @__PURE__ */ jsx("div", {
2414
2709
  className: "font-medium",
@@ -2440,6 +2735,69 @@ const SurveyAssistantPanel = ({ language, localizations, ...props }) => /* @__PU
2440
2735
  })
2441
2736
  });
2442
2737
  //#endregion
2738
+ //#region src/ui/survey-assistant-thread-launcher-prompt.tsx
2739
+ const SurveyAssistantThreadLauncherPromptContent = ({ className, disabled = false, modelCatalogApi, modelStorageKey, onError, onModelCatalogError, onSubmitted, onValueChange, placeholder, textareaClassName, textareaProps, textareaRef, threadLauncher, uiContext, value }) => {
2740
+ const catalog = useSurveyAssistantModelCatalog({
2741
+ api: modelCatalogApi,
2742
+ storageKey: modelStorageKey
2743
+ });
2744
+ const [submitting, setSubmitting] = useState(false);
2745
+ useEffect(() => {
2746
+ if (catalog.error) onModelCatalogError?.(catalog.error);
2747
+ }, [catalog.error, onModelCatalogError]);
2748
+ const submit = useCallback(async ({ files, text }) => {
2749
+ if (disabled || submitting) return false;
2750
+ setSubmitting(true);
2751
+ try {
2752
+ await threadLauncher.startThread({
2753
+ files,
2754
+ modelId: catalog.modelId,
2755
+ prompt: text,
2756
+ uiContext
2757
+ });
2758
+ onSubmitted();
2759
+ return true;
2760
+ } catch (error) {
2761
+ onError?.(error);
2762
+ return false;
2763
+ } finally {
2764
+ setSubmitting(false);
2765
+ }
2766
+ }, [
2767
+ catalog.modelId,
2768
+ disabled,
2769
+ onError,
2770
+ onSubmitted,
2771
+ submitting,
2772
+ threadLauncher,
2773
+ uiContext
2774
+ ]);
2775
+ return /* @__PURE__ */ jsx(SurveyAssistantPromptInput, {
2776
+ attachmentsDisabled: submitting,
2777
+ className,
2778
+ disabled: disabled || submitting,
2779
+ modelDisabled: catalog.loading || submitting,
2780
+ modelId: catalog.modelId,
2781
+ models: catalog.models,
2782
+ onModelChange: catalog.selectModel,
2783
+ onSubmit: submit,
2784
+ onValueChange,
2785
+ placeholder,
2786
+ textareaClassName,
2787
+ textareaProps,
2788
+ textareaRef,
2789
+ value
2790
+ });
2791
+ };
2792
+ const SurveyAssistantThreadLauncherPrompt = ({ language, localizations, ...props }) => /* @__PURE__ */ jsx("div", {
2793
+ className: "case-survey-assistant",
2794
+ children: /* @__PURE__ */ jsx(SurveyAssistantLocalizationProvider, {
2795
+ language,
2796
+ localizations,
2797
+ children: /* @__PURE__ */ jsx(SurveyAssistantThreadLauncherPromptContent, { ...props })
2798
+ })
2799
+ });
2800
+ //#endregion
2443
2801
  //#region src/ui/connected-survey-assistant-panel.tsx
2444
2802
  const ConnectedSurveyAssistantPanel = ({ modelCatalogApi, modelStorageKey, onModelCatalogError, onModelChange, ...props }) => {
2445
2803
  const catalog = useSurveyAssistantModelCatalog({
@@ -2458,6 +2816,6 @@ const ConnectedSurveyAssistantPanel = ({ modelCatalogApi, modelStorageKey, onMod
2458
2816
  });
2459
2817
  };
2460
2818
  //#endregion
2461
- export { ConnectedSurveyAssistantPanel, SurveyAssistantLocalizationProvider, SurveyAssistantPanel, defaultSurveyAssistantLocalizations, surveyAssistantEnMessages, surveyAssistantNlMessages, useSurveyAssistantTranslation };
2819
+ export { ConnectedSurveyAssistantPanel, SurveyAssistantLocalizationProvider, SurveyAssistantPanel, SurveyAssistantPromptInput, SurveyAssistantThreadLauncherPrompt, defaultSurveyAssistantLocalizations, surveyAssistantEnMessages, surveyAssistantNlMessages, useSurveyAssistantTranslation };
2462
2820
 
2463
2821
  //# sourceMappingURL=ui.mjs.map