@case-framework/survey-assistant 0.6.0 → 0.8.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 (135) hide show
  1. package/README.md +67 -7
  2. package/dist/{attachments-B0hg3vpT.mjs → attachments-BuNni5vB.mjs} +1 -1
  3. package/dist/{attachments-B0hg3vpT.mjs.map → attachments-BuNni5vB.mjs.map} +1 -1
  4. package/dist/{authoring-references-CW38pYzK.mjs → authoring-references-DbuOKerg.mjs} +13 -4
  5. package/dist/authoring-references-DbuOKerg.mjs.map +1 -0
  6. package/dist/{capabilities-DVTfogjv.d.mts → capabilities-CmeeAjEc.d.mts} +3 -4
  7. package/dist/capabilities-CmeeAjEc.d.mts.map +1 -0
  8. package/dist/capabilities-DCMA71PP.mjs +58 -0
  9. package/dist/capabilities-DCMA71PP.mjs.map +1 -0
  10. package/dist/capabilities-default.d.mts +13 -12
  11. package/dist/capabilities-default.d.mts.map +1 -1
  12. package/dist/capabilities-default.mjs +2 -414
  13. package/dist/{constants-BMGTgfjv.d.mts → constants-B6HzpEsx.d.mts} +7 -28
  14. package/dist/constants-B6HzpEsx.d.mts.map +1 -0
  15. package/dist/{constants-0UHGFcRJ.mjs → constants-Ct-vv9cu.mjs} +1 -1
  16. package/dist/{constants-0UHGFcRJ.mjs.map → constants-Ct-vv9cu.mjs.map} +1 -1
  17. package/dist/controller-proxy-D7uQfm5f.d.mts +65 -0
  18. package/dist/controller-proxy-D7uQfm5f.d.mts.map +1 -0
  19. package/dist/{controller-proxy-DMTqer6T.mjs → controller-proxy-DEeFl9IA.mjs} +29 -26
  20. package/dist/controller-proxy-DEeFl9IA.mjs.map +1 -0
  21. package/dist/default-fhSon2RM.mjs +760 -0
  22. package/dist/default-fhSon2RM.mjs.map +1 -0
  23. package/dist/{digest-SJmoYVaK.d.mts → digest-C39WyAWG.d.mts} +2 -3
  24. package/dist/digest-C39WyAWG.d.mts.map +1 -0
  25. package/dist/digest.d.mts +1 -1
  26. package/dist/digest.mjs.map +1 -1
  27. package/dist/{engine-CQwOAcwK.mjs → engine-nXoCWYpQ.mjs} +2821 -2018
  28. package/dist/engine-nXoCWYpQ.mjs.map +1 -0
  29. package/dist/engine.d.mts +3 -3
  30. package/dist/engine.mjs +3 -3
  31. package/dist/{fair-guidance-CRYCRxQm.mjs → fair-guidance-BmO4PswW.mjs} +1 -1
  32. package/dist/{fair-guidance-CRYCRxQm.mjs.map → fair-guidance-BmO4PswW.mjs.map} +1 -1
  33. package/dist/{index-D6nqkz0O.d.mts → index-Ciq7vHIs.d.mts} +121 -197
  34. package/dist/index-Ciq7vHIs.d.mts.map +1 -0
  35. package/dist/{index-DvTqML-0.d.mts → index-D7wT6P3t.d.mts} +294 -63
  36. package/dist/index-D7wT6P3t.d.mts.map +1 -0
  37. package/dist/index-WB5kfnz0.d.mts +348 -0
  38. package/dist/index-WB5kfnz0.d.mts.map +1 -0
  39. package/dist/{index-B0lxsAzx.d.mts → index-r5riv4md.d.mts} +50 -29
  40. package/dist/index-r5riv4md.d.mts.map +1 -0
  41. package/dist/{lifecycle-BmGAIUlv.d.mts → lifecycle-Dm8u7QFh.d.mts} +40 -18
  42. package/dist/lifecycle-Dm8u7QFh.d.mts.map +1 -0
  43. package/dist/{memory-thread-repository-CjFGVNZh.mjs → memory-thread-repository-DAnOGIB3.mjs} +89 -34
  44. package/dist/memory-thread-repository-DAnOGIB3.mjs.map +1 -0
  45. package/dist/{protocol-DJUP4gc0.mjs → protocol-CF4Bh_Ch.mjs} +182 -50
  46. package/dist/protocol-CF4Bh_Ch.mjs.map +1 -0
  47. package/dist/protocol.d.mts +4 -4
  48. package/dist/protocol.mjs +3 -3
  49. package/dist/react-C_GY-Fsc.mjs +1727 -0
  50. package/dist/react-C_GY-Fsc.mjs.map +1 -0
  51. package/dist/react-integration.d.mts +2 -2
  52. package/dist/react-integration.mjs +2 -2
  53. package/dist/react.d.mts +3 -3
  54. package/dist/react.mjs +3 -3
  55. package/dist/references/assistant-operations.md +143 -68
  56. package/dist/references/core-rules.md +6 -3
  57. package/dist/references/embedded-forms.md +34 -14
  58. package/dist/references/expressions.md +238 -48
  59. package/dist/references/fair-by-design.md +9 -9
  60. package/dist/references/item-types.md +306 -72
  61. package/dist/references/localization.md +5 -4
  62. package/dist/references/response-variables.md +38 -0
  63. package/dist/references/rich-text-content.md +60 -27
  64. package/dist/references/source-material-surveys.md +13 -5
  65. package/dist/references/survey-data-model.md +32 -9
  66. package/dist/{request-context-BjTHNcDd.mjs → request-context-Dg4jSwN1.mjs} +4 -10
  67. package/dist/request-context-Dg4jSwN1.mjs.map +1 -0
  68. package/dist/{request-guardrails-TmgQtqp4.mjs → request-guardrails-C0ugBOp0.mjs} +6 -5
  69. package/dist/request-guardrails-C0ugBOp0.mjs.map +1 -0
  70. package/dist/server-agent.d.mts +53 -44
  71. package/dist/server-agent.d.mts.map +1 -1
  72. package/dist/server-agent.mjs +48 -27
  73. package/dist/server-agent.mjs.map +1 -1
  74. package/dist/server-runtime.d.mts +36 -49
  75. package/dist/server-runtime.d.mts.map +1 -1
  76. package/dist/server-runtime.mjs +17 -12
  77. package/dist/server-runtime.mjs.map +1 -1
  78. package/dist/server-tasks.d.mts +2 -2
  79. package/dist/server-tasks.mjs +2 -313
  80. package/dist/server-tools.d.mts +1 -1
  81. package/dist/server-tools.mjs +1 -1
  82. package/dist/server.d.mts +13 -36
  83. package/dist/server.d.mts.map +1 -1
  84. package/dist/server.mjs +5 -5
  85. package/dist/server.mjs.map +1 -1
  86. package/dist/storage-postgres.d.mts +9 -25
  87. package/dist/storage-postgres.d.mts.map +1 -1
  88. package/dist/storage-postgres.mjs +5 -4
  89. package/dist/storage-postgres.mjs.map +1 -1
  90. package/dist/{tasks-DxzD25Zc.d.mts → tasks-B9h3LPam.d.mts} +19 -35
  91. package/dist/tasks-B9h3LPam.d.mts.map +1 -0
  92. package/dist/tasks-BZEiwgxA.mjs +396 -0
  93. package/dist/tasks-BZEiwgxA.mjs.map +1 -0
  94. package/dist/thread-documents-DrHbaXqP.d.mts +119 -0
  95. package/dist/thread-documents-DrHbaXqP.d.mts.map +1 -0
  96. package/dist/{thread-handlers-AbQbt4sX.d.mts → thread-handlers-Bef0td9N.d.mts} +10 -40
  97. package/dist/thread-handlers-Bef0td9N.d.mts.map +1 -0
  98. package/dist/{tools-CFh35R4d.mjs → tools-CEsv-WoO.mjs} +705 -578
  99. package/dist/tools-CEsv-WoO.mjs.map +1 -0
  100. package/dist/ui.css +1 -1
  101. package/dist/ui.d.mts +17 -55
  102. package/dist/ui.d.mts.map +1 -1
  103. package/dist/ui.mjs +759 -171
  104. package/dist/ui.mjs.map +1 -1
  105. package/docs/integration.md +51 -1
  106. package/package.json +49 -42
  107. package/dist/authoring-references-CW38pYzK.mjs.map +0 -1
  108. package/dist/capabilities-DVTfogjv.d.mts.map +0 -1
  109. package/dist/capabilities-default.mjs.map +0 -1
  110. package/dist/constants-BMGTgfjv.d.mts.map +0 -1
  111. package/dist/controller-proxy-DMTqer6T.mjs.map +0 -1
  112. package/dist/controller-proxy-mRvez2qw.d.mts +0 -85
  113. package/dist/controller-proxy-mRvez2qw.d.mts.map +0 -1
  114. package/dist/digest-SJmoYVaK.d.mts.map +0 -1
  115. package/dist/engine-CQwOAcwK.mjs.map +0 -1
  116. package/dist/form-authoring-kQDgC5oz.mjs +0 -155
  117. package/dist/form-authoring-kQDgC5oz.mjs.map +0 -1
  118. package/dist/index-B0lxsAzx.d.mts.map +0 -1
  119. package/dist/index-BS-Xjk1h.d.mts +0 -224
  120. package/dist/index-BS-Xjk1h.d.mts.map +0 -1
  121. package/dist/index-D6nqkz0O.d.mts.map +0 -1
  122. package/dist/index-DvTqML-0.d.mts.map +0 -1
  123. package/dist/lifecycle-BmGAIUlv.d.mts.map +0 -1
  124. package/dist/memory-thread-repository-CjFGVNZh.mjs.map +0 -1
  125. package/dist/protocol-DJUP4gc0.mjs.map +0 -1
  126. package/dist/react-Dqlmasrs.mjs +0 -768
  127. package/dist/react-Dqlmasrs.mjs.map +0 -1
  128. package/dist/request-context-BjTHNcDd.mjs.map +0 -1
  129. package/dist/request-guardrails-TmgQtqp4.mjs.map +0 -1
  130. package/dist/server-tasks.mjs.map +0 -1
  131. package/dist/tasks-DxzD25Zc.d.mts.map +0 -1
  132. package/dist/thread-documents-rmd6nyX9.d.mts +0 -107
  133. package/dist/thread-documents-rmd6nyX9.d.mts.map +0 -1
  134. package/dist/thread-handlers-AbQbt4sX.d.mts.map +0 -1
  135. package/dist/tools-CFh35R4d.mjs.map +0 -1
package/dist/ui.mjs CHANGED
@@ -1,22 +1,26 @@
1
1
  "use client";
2
- import { jt as parseSurveyAssistantError, l as surveyAssistantTurnFinalSchema, n as SURVEY_ASSISTANT_TURN_FINAL_PART_TYPE } from "./protocol-DJUP4gc0.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";
2
+ import { Ft as isSurveyAssistantErrorRetryable, It as parseSurveyAssistantError, Vt as getSurveyAssistantEmbeddedBinaryMarker, l as surveyAssistantTurnFinalSchema, n as SURVEY_ASSISTANT_TURN_FINAL_PART_TYPE } from "./protocol-CF4Bh_Ch.mjs";
3
+ import { n as isSurveyAssistantAttachmentMediaType, t as SURVEY_ASSISTANT_ATTACHMENT_MEDIA_TYPES } from "./attachments-BuNni5vB.mjs";
4
+ import { E as isSurveyAssistantTurnActive, S as parseSurveyChangeSet, T as unwrapSurveyAssistantToolOutput, _ as createSurveyAssistantThreadClient, a as createSurveyAssistantSupportDetails, c as estimateSurveyAssistantSupportBundleBytes, d as prepareSurveyAssistantSupportBundle, i as SURVEY_ASSISTANT_DEFAULT_SUPPORT_BUNDLE_SELECTION, l as generateSurveyAssistantSupportBundle, m as createSurveyAssistantDiagnosticStore, o as createSurveyAssistantSupportMailto, r as useSurveyAssistantModelCatalog, s as downloadSurveyAssistantSupportBundle, t as useSurveyAssistantSession, u as isSurveyAssistantSupportBundleEnabled } from "./react-C_GY-Fsc.mjs";
5
+ import { a as connectSurveyAssistantThreadLauncher } from "./controller-proxy-DEeFl9IA.mjs";
6
6
  import { getToolName, isFileUIPart, isReasoningUIPart, isToolUIPart } from "ai";
7
7
  import { createContext, createElement, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
8
- import { Collapsible, Select, Slot, Tooltip } from "radix-ui";
8
+ import { Collapsible } from "@base-ui/react/collapsible";
9
9
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
10
10
  import { clsx } from "clsx";
11
11
  import { twMerge } from "tailwind-merge";
12
- import { useControllableState } from "@radix-ui/react-use-controllable-state";
13
- 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";
12
+ import { ArrowDownIcon, ArrowUpIcon, BotIcon, BrainIcon, CheckCircleIcon, CheckIcon, ChevronDownIcon, ChevronRightIcon, ChevronUpIcon, ClockIcon, CopyIcon, DotIcon, DownloadIcon, FileIcon, HistoryIcon, ListChecksIcon, LoaderCircleIcon, MailIcon, MessageSquarePlusIcon, PackageOpenIcon, PaperclipIcon, PencilIcon, PlusIcon, SearchIcon, SparklesIcon, SquareIcon, Trash2Icon, WandSparklesIcon, WrenchIcon, XCircleIcon, XIcon } from "lucide-react";
14
13
  import { motion } from "motion/react";
15
14
  import { createLocalizationTranslator, mergeLocalizationCatalogs } from "@case-framework/ui-localization";
16
15
  import { cva } from "class-variance-authority";
16
+ import { Button } from "@base-ui/react/button";
17
17
  import { StickToBottom, useStickToBottomContext } from "use-stick-to-bottom";
18
+ import { Tooltip } from "@base-ui/react/tooltip";
19
+ import { Select } from "@base-ui/react/select";
18
20
  import { Streamdown } from "streamdown";
19
21
  import { bundledLanguages } from "shiki/langs";
22
+ import { Checkbox } from "@base-ui/react/checkbox";
23
+ import { Dialog } from "@base-ui/react/dialog";
20
24
  //#region src/ui/internal/collapsible.tsx
21
25
  function Collapsible$1({ ...props }) {
22
26
  return /* @__PURE__ */ jsx(Collapsible.Root, {
@@ -25,13 +29,13 @@ function Collapsible$1({ ...props }) {
25
29
  });
26
30
  }
27
31
  function CollapsibleTrigger({ ...props }) {
28
- return /* @__PURE__ */ jsx(Collapsible.CollapsibleTrigger, {
32
+ return /* @__PURE__ */ jsx(Collapsible.Trigger, {
29
33
  "data-slot": "collapsible-trigger",
30
34
  ...props
31
35
  });
32
36
  }
33
37
  function CollapsibleContent({ ...props }) {
34
- return /* @__PURE__ */ jsx(Collapsible.CollapsibleContent, {
38
+ return /* @__PURE__ */ jsx(Collapsible.Panel, {
35
39
  "data-slot": "collapsible-content",
36
40
  ...props
37
41
  });
@@ -247,6 +251,11 @@ const surveyAssistantEnMessages = {
247
251
  dismiss: "Dismiss",
248
252
  dismissAssistant: "Dismiss assistant error",
249
253
  retry: "Try again",
254
+ exportDetails: "Export details",
255
+ showProviderDetails: "Show full provider message",
256
+ hideProviderDetails: "Hide provider message",
257
+ upstreamStatus: "Provider status {status}",
258
+ providerRequestId: "Request {requestId}",
250
259
  fallbackDetail: "The assistant hit an error on the previous turn.",
251
260
  model_unavailable: {
252
261
  title: "Model unavailable",
@@ -272,6 +281,7 @@ const surveyAssistantEnMessages = {
272
281
  threads: {
273
282
  newChat: "New chat",
274
283
  history: "Chat history",
284
+ exportSupportBundle: "Export support bundle",
275
285
  recent: "Recent chats",
276
286
  search: "Search chats",
277
287
  searching: "Searching older chats…",
@@ -285,6 +295,48 @@ const surveyAssistantEnMessages = {
285
295
  delete: "Delete {title}",
286
296
  deleteTitle: "Delete chat",
287
297
  deleteConfirm: "Delete \"{title}\" and its attachments?"
298
+ },
299
+ supportBundle: {
300
+ title: "Export support bundle",
301
+ description: "Choose which broad data categories to include. The ZIP is created and downloaded in this browser.",
302
+ close: "Close export dialog",
303
+ preparing: "Preparing diagnostic data…",
304
+ preparationFailed: "The support bundle could not be prepared",
305
+ generationFailed: "The support bundle could not be generated.",
306
+ chooseData: "Data to include",
307
+ core: {
308
+ title: "Core technical diagnostics (always included)",
309
+ description: "Request and turn identifiers, timing, status and error details, counters, and availability information."
310
+ },
311
+ categories: {
312
+ conversation: {
313
+ title: "Conversation and agent activity",
314
+ description: "Messages, visible reasoning, tool activity, and apply results."
315
+ },
316
+ survey: {
317
+ title: "Survey data",
318
+ description: "The submitted survey and a snapshot captured when this dialog opened."
319
+ },
320
+ attachments: {
321
+ title: "Attachments",
322
+ description: "Available source PDFs and images from this browser session."
323
+ }
324
+ },
325
+ sensitiveNote: "Conversation, reasoning, surveys, and attachments can contain sensitive research or authoring data. Deselect a category if you do not want to share it.",
326
+ estimatedSize: "Estimated ZIP size",
327
+ emailWarning: "This may be too large for some email providers.",
328
+ tooLarge: "The selection is too large. Deselect a category before downloading.",
329
+ unavailable: "{count} diagnostic component is unavailable or was truncated.",
330
+ unavailablePlural: "{count} diagnostic components are unavailable or were truncated.",
331
+ localOnly: "Nothing is sent automatically. You decide whether and how to share the downloaded ZIP.",
332
+ download: "Download support bundle",
333
+ downloadAgain: "Download again",
334
+ downloaded: "Downloaded {filename}. Attach it manually when contacting support.",
335
+ openEmail: "Open email draft",
336
+ supportDetails: "Support details to include with your request",
337
+ copyDetails: "Copy support details",
338
+ detailsCopied: "Support details copied",
339
+ copyDetailsFailed: "The support details could not be copied. Select and copy them manually."
288
340
  }
289
341
  };
290
342
  //#endregion
@@ -464,6 +516,11 @@ const surveyAssistantNlMessages = {
464
516
  dismiss: "Sluiten",
465
517
  dismissAssistant: "Assistentfout sluiten",
466
518
  retry: "Opnieuw proberen",
519
+ exportDetails: "Details exporteren",
520
+ showProviderDetails: "Volledig providerbericht tonen",
521
+ hideProviderDetails: "Providerbericht verbergen",
522
+ upstreamStatus: "Providerstatus {status}",
523
+ providerRequestId: "Verzoek {requestId}",
467
524
  fallbackDetail: "Er is tijdens de vorige beurt een fout opgetreden.",
468
525
  model_unavailable: {
469
526
  title: "Model niet beschikbaar",
@@ -489,6 +546,7 @@ const surveyAssistantNlMessages = {
489
546
  threads: {
490
547
  newChat: "Nieuwe chat",
491
548
  history: "Chatgeschiedenis",
549
+ exportSupportBundle: "Supportbundel exporteren",
492
550
  recent: "Recente chats",
493
551
  search: "Chats zoeken",
494
552
  searching: "Oudere chats zoeken…",
@@ -502,6 +560,48 @@ const surveyAssistantNlMessages = {
502
560
  delete: "{title} verwijderen",
503
561
  deleteTitle: "Chat verwijderen",
504
562
  deleteConfirm: "\"{title}\" en de bijlagen verwijderen?"
563
+ },
564
+ supportBundle: {
565
+ title: "Supportbundel exporteren",
566
+ description: "Kies welke brede gegevenscategorieën worden opgenomen. Het ZIP-bestand wordt in deze browser gemaakt en gedownload.",
567
+ close: "Exportvenster sluiten",
568
+ preparing: "Diagnostische gegevens voorbereiden…",
569
+ preparationFailed: "De supportbundel kon niet worden voorbereid",
570
+ generationFailed: "De supportbundel kon niet worden gemaakt.",
571
+ chooseData: "Op te nemen gegevens",
572
+ core: {
573
+ title: "Technische basisgegevens (altijd opgenomen)",
574
+ description: "Verzoek- en beurt-ID's, timing, status- en foutdetails, aantallen en beschikbaarheid."
575
+ },
576
+ categories: {
577
+ conversation: {
578
+ title: "Gesprek en agentactiviteit",
579
+ description: "Berichten, zichtbare redenering, hulpmiddelactiviteit en toepassingsresultaten."
580
+ },
581
+ survey: {
582
+ title: "Surveygegevens",
583
+ description: "De verzonden survey en een momentopname van toen dit venster werd geopend."
584
+ },
585
+ attachments: {
586
+ title: "Bijlagen",
587
+ description: "Beschikbare bron-pdf's en afbeeldingen uit deze browsersessie."
588
+ }
589
+ },
590
+ sensitiveNote: "Gesprek, redenering, surveys en bijlagen kunnen gevoelige onderzoeks- of auteursgegevens bevatten. Schakel een categorie uit als je die niet wilt delen.",
591
+ estimatedSize: "Geschatte ZIP-grootte",
592
+ emailWarning: "Dit bestand kan te groot zijn voor sommige e-mailproviders.",
593
+ tooLarge: "De selectie is te groot. Schakel een categorie uit voordat je downloadt.",
594
+ unavailable: "{count} diagnostisch onderdeel is niet beschikbaar of is ingekort.",
595
+ unavailablePlural: "{count} diagnostische onderdelen zijn niet beschikbaar of zijn ingekort.",
596
+ localOnly: "Er wordt niets automatisch verzonden. Je bepaalt zelf of en hoe je het gedownloade ZIP-bestand deelt.",
597
+ download: "Supportbundel downloaden",
598
+ downloadAgain: "Opnieuw downloaden",
599
+ downloaded: "{filename} is gedownload. Voeg het handmatig toe wanneer je contact opneemt met support.",
600
+ openEmail: "E-mailconcept openen",
601
+ supportDetails: "Supportgegevens voor je verzoek",
602
+ copyDetails: "Supportgegevens kopiëren",
603
+ detailsCopied: "Supportgegevens gekopieerd",
604
+ copyDetailsFailed: "De supportgegevens konden niet worden gekopieerd. Selecteer en kopieer ze handmatig."
505
605
  }
506
606
  };
507
607
  //#endregion
@@ -549,15 +649,18 @@ const formatDuration = (durationSeconds) => {
549
649
  const ChainOfThought = memo(({ className, isStreaming = false, open, defaultOpen, defaultDuration, onOpenChange, duration: durationProp, autoClose = true, children, ...props }) => {
550
650
  const resolvedDefaultOpen = defaultOpen ?? isStreaming;
551
651
  const isExplicitlyClosed = defaultOpen === false;
552
- const [isOpen, setIsOpen] = useControllableState({
553
- defaultProp: resolvedDefaultOpen,
554
- onChange: onOpenChange,
555
- prop: open
556
- });
557
- const [duration, setDuration] = useControllableState({
558
- defaultProp: defaultDuration,
559
- prop: durationProp
560
- });
652
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(resolvedDefaultOpen);
653
+ const isOpen = open ?? uncontrolledOpen;
654
+ const setIsOpen = useCallback((nextOpen) => {
655
+ if (open === void 0) setUncontrolledOpen(nextOpen);
656
+ if (nextOpen !== isOpen) onOpenChange?.(nextOpen);
657
+ }, [
658
+ open,
659
+ isOpen,
660
+ onOpenChange
661
+ ]);
662
+ const [measuredDuration, setDuration] = useState(defaultDuration);
663
+ const duration = durationProp ?? measuredDuration;
561
664
  const hasEverStreamedRef = useRef(isStreaming);
562
665
  const [hasAutoClosed, setHasAutoClosed] = useState(false);
563
666
  const startTimeRef = useRef(null);
@@ -639,7 +742,7 @@ const ChainOfThoughtHeader = memo(({ className, children, getThinkingMessage, ..
639
742
  });
640
743
  });
641
744
  const ChainOfThoughtContent = memo(({ className, ...props }) => /* @__PURE__ */ jsx(CollapsibleContent, {
642
- className: cn("mt-2", "data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 outline-none data-[state=closed]:animate-out data-[state=open]:animate-in", className),
745
+ className: cn("mt-2", "data-ending-style:opacity-0 data-ending-style:-translate-y-2 data-starting-style:-translate-y-2 outline-none transition-[opacity,transform,scale] ease-out motion-reduce:transition-none", className),
643
746
  ...props
644
747
  }));
645
748
  const stepStatusStyles = {
@@ -701,8 +804,8 @@ const buttonVariants = cva("inline-flex shrink-0 items-center justify-center gap
701
804
  size: "default"
702
805
  }
703
806
  });
704
- const Button = ({ className, variant = "default", size = "default", asChild = false, ...props }) => {
705
- return /* @__PURE__ */ jsx(asChild ? Slot.Root : "button", {
807
+ const Button$1 = ({ className, variant = "default", size = "default", ...props }) => {
808
+ return /* @__PURE__ */ jsx(Button, {
706
809
  className: cn(buttonVariants({
707
810
  variant,
708
811
  size,
@@ -752,7 +855,7 @@ const ConversationScrollButton = ({ className, ...props }) => {
752
855
  const handleScrollToBottom = useCallback(() => {
753
856
  scrollToBottom();
754
857
  }, [scrollToBottom]);
755
- return !isAtBottom && /* @__PURE__ */ jsx(Button, {
858
+ return !isAtBottom && /* @__PURE__ */ jsx(Button$1, {
756
859
  className: cn("absolute left-1/2 z-10 size-8 -translate-x-1/2 rounded-full border border-background/60 bg-foreground text-background shadow-lg ring-1 ring-border/50 hover:bg-foreground/85 hover:text-background", className),
757
860
  "aria-label": t("conversation.scrollToLatest"),
758
861
  onClick: handleScrollToBottom,
@@ -777,17 +880,43 @@ const MessageContent = ({ children, className, ...props }) => /* @__PURE__ */ js
777
880
  });
778
881
  //#endregion
779
882
  //#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,
883
+ function TooltipProvider({ delay = 700, ...props }) {
884
+ return /* @__PURE__ */ jsx(Tooltip.Provider, {
885
+ "data-slot": "tooltip-provider",
886
+ delay,
788
887
  ...props
789
- })
790
- }) });
888
+ });
889
+ }
890
+ function Tooltip$1({ ...props }) {
891
+ return /* @__PURE__ */ jsx(Tooltip.Root, {
892
+ "data-slot": "tooltip",
893
+ ...props
894
+ });
895
+ }
896
+ function TooltipTrigger({ ...props }) {
897
+ return /* @__PURE__ */ jsx(Tooltip.Trigger, {
898
+ "data-slot": "tooltip-trigger",
899
+ ...props
900
+ });
901
+ }
902
+ function TooltipContent({ className, side = "top", sideOffset = 6, align = "center", alignOffset = 0, children, ...props }) {
903
+ return /* @__PURE__ */ jsx(Tooltip.Portal, { children: /* @__PURE__ */ jsx("div", {
904
+ className: "case-survey-assistant case-survey-assistant-portal",
905
+ children: /* @__PURE__ */ jsx(Tooltip.Positioner, {
906
+ align,
907
+ alignOffset,
908
+ side,
909
+ sideOffset,
910
+ className: "isolate z-50",
911
+ children: /* @__PURE__ */ jsx(Tooltip.Popup, {
912
+ "data-slot": "tooltip-content",
913
+ 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-ending-style:opacity-0 transition-[opacity,transform,scale] ease-out motion-reduce:transition-none data-starting-style:opacity-0", className),
914
+ ...props,
915
+ children
916
+ })
917
+ })
918
+ }) });
919
+ }
791
920
  //#endregion
792
921
  //#region src/ui/internal/prompt-input.tsx
793
922
  const SUPPORTED_FILE_ACCEPT = [
@@ -797,7 +926,7 @@ const SUPPORTED_FILE_ACCEPT = [
797
926
  ".jpeg",
798
927
  ...SURVEY_ASSISTANT_ATTACHMENT_MEDIA_TYPES
799
928
  ].join(",");
800
- const WORD_DOCUMENT_MEDIA_TYPES = new Set([
929
+ const WORD_DOCUMENT_MEDIA_TYPES = /* @__PURE__ */ new Set([
801
930
  "application/msword",
802
931
  "application/vnd.ms-word",
803
932
  "application/vnd.ms-word.document.macroenabled.12",
@@ -810,8 +939,8 @@ const getUnsupportedAttachmentKind = ({ name, type }) => {
810
939
  return "other";
811
940
  };
812
941
  const formatFileSize = (size) => {
813
- if (size < 1024 * 1024) return `${Math.max(1, Math.round(size / 1024))} KB`;
814
- return `${(size / (1024 * 1024)).toFixed(size < 10 * 1024 * 1024 ? 1 : 0)} MB`;
942
+ if (size < 1048576) return `${Math.max(1, Math.round(size / 1024))} KB`;
943
+ return `${(size / 1048576).toFixed(size < 10485760 ? 1 : 0)} MB`;
815
944
  };
816
945
  const fileToUIPart = async (file) => {
817
946
  const mediaType = file.type || "application/octet-stream";
@@ -1015,32 +1144,29 @@ const PromptInputAttachmentButton = ({ accept = SUPPORTED_FILE_ACCEPT, className
1015
1144
  const { addFiles } = usePromptInput();
1016
1145
  const t = useSurveyAssistantTranslation();
1017
1146
  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
- ]
1043
- })
1147
+ delay: 300,
1148
+ children: /* @__PURE__ */ jsxs(Tooltip$1, { children: [/* @__PURE__ */ jsxs(TooltipTrigger, {
1149
+ render: /* @__PURE__ */ jsx("label", { 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) }),
1150
+ children: [
1151
+ /* @__PURE__ */ jsx(PaperclipIcon, { className: "size-4" }),
1152
+ /* @__PURE__ */ jsx("span", {
1153
+ className: "sr-only",
1154
+ children: t("input.attachFiles")
1155
+ }),
1156
+ /* @__PURE__ */ jsx("input", {
1157
+ accept,
1158
+ className: "sr-only",
1159
+ disabled,
1160
+ multiple: true,
1161
+ onChange: (event) => {
1162
+ addFiles(event.currentTarget.files);
1163
+ onChange?.(event);
1164
+ event.currentTarget.value = "";
1165
+ },
1166
+ type: "file",
1167
+ ...props
1168
+ })
1169
+ ]
1044
1170
  }), /* @__PURE__ */ jsx(TooltipContent, {
1045
1171
  align: "start",
1046
1172
  side: "top",
@@ -1071,15 +1197,18 @@ const PromptInputSubmit = ({ className, status, onStop, onClick, ...props }) =>
1071
1197
  };
1072
1198
  //#endregion
1073
1199
  //#region src/ui/internal/select.tsx
1074
- function Select$1({ ...props }) {
1075
- return /* @__PURE__ */ jsx(Select.Root, {
1076
- "data-slot": "select",
1200
+ const Select$1 = Select.Root;
1201
+ function SelectGroup({ className, ...props }) {
1202
+ return /* @__PURE__ */ jsx(Select.Group, {
1203
+ "data-slot": "select-group",
1204
+ className: cn("scroll-my-1 p-1", className),
1077
1205
  ...props
1078
1206
  });
1079
1207
  }
1080
- function SelectValue({ ...props }) {
1208
+ function SelectValue({ className, ...props }) {
1081
1209
  return /* @__PURE__ */ jsx(Select.Value, {
1082
1210
  "data-slot": "select-value",
1211
+ className: cn("flex flex-1 text-left", className),
1083
1212
  ...props
1084
1213
  });
1085
1214
  }
@@ -1089,29 +1218,30 @@ function SelectTrigger({ className, size = "default", children, ...props }) {
1089
1218
  "data-size": size,
1090
1219
  className: cn("flex w-fit items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground", className),
1091
1220
  ...props,
1092
- children: [children, /* @__PURE__ */ jsx(Select.Icon, {
1093
- asChild: true,
1094
- children: /* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-4 opacity-50" })
1095
- })]
1221
+ children: [children, /* @__PURE__ */ jsx(Select.Icon, { render: /* @__PURE__ */ jsx(ChevronDownIcon, { className: "pointer-events-none size-4 text-muted-foreground" }) })]
1096
1222
  });
1097
1223
  }
1098
- function SelectContent({ className, children, position = "item-aligned", align = "center", ...props }) {
1224
+ function SelectContent({ className, children, side = "bottom", sideOffset = 4, align = "center", alignOffset = 0, alignItemWithTrigger = true, ...props }) {
1099
1225
  return /* @__PURE__ */ jsx(Select.Portal, { children: /* @__PURE__ */ jsx("div", {
1100
1226
  className: "case-survey-assistant case-survey-assistant-portal",
1101
- children: /* @__PURE__ */ jsxs(Select.Content, {
1102
- "data-slot": "select-content",
1103
- className: cn("relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95", position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className),
1104
- position,
1227
+ children: /* @__PURE__ */ jsx(Select.Positioner, {
1228
+ side,
1229
+ sideOffset,
1105
1230
  align,
1106
- ...props,
1107
- children: [
1108
- /* @__PURE__ */ jsx(SelectScrollUpButton, {}),
1109
- /* @__PURE__ */ jsx(Select.Viewport, {
1110
- className: cn("p-1", position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"),
1111
- children
1112
- }),
1113
- /* @__PURE__ */ jsx(SelectScrollDownButton, {})
1114
- ]
1231
+ alignOffset,
1232
+ alignItemWithTrigger,
1233
+ className: "isolate z-50",
1234
+ children: /* @__PURE__ */ jsxs(Select.Popup, {
1235
+ "data-slot": "select-content",
1236
+ "data-align-trigger": alignItemWithTrigger,
1237
+ className: cn("relative z-50 max-h-(--available-height) min-w-[8rem] origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover text-popover-foreground shadow-md data-ending-style:opacity-0 data-ending-style:scale-95 transition-[opacity,transform,scale] ease-out motion-reduce:transition-none data-starting-style:opacity-0 data-starting-style:scale-95", className),
1238
+ ...props,
1239
+ children: [
1240
+ /* @__PURE__ */ jsx(SelectScrollUpButton, {}),
1241
+ /* @__PURE__ */ jsx(Select.List, { children }),
1242
+ /* @__PURE__ */ jsx(SelectScrollDownButton, {})
1243
+ ]
1244
+ })
1115
1245
  })
1116
1246
  }) });
1117
1247
  }
@@ -1120,27 +1250,29 @@ function SelectItem({ className, children, ...props }) {
1120
1250
  "data-slot": "select-item",
1121
1251
  className: cn("relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2", className),
1122
1252
  ...props,
1123
- children: [/* @__PURE__ */ jsx("span", {
1124
- "data-slot": "select-item-indicator",
1125
- className: "absolute right-2 flex size-3.5 items-center justify-center",
1126
- children: /* @__PURE__ */ jsx(Select.ItemIndicator, { children: /* @__PURE__ */ jsx(CheckIcon, { className: "size-4" }) })
1127
- }), /* @__PURE__ */ jsx(Select.ItemText, { children })]
1253
+ children: [/* @__PURE__ */ jsx(Select.ItemText, {
1254
+ className: "flex flex-1 shrink-0 gap-2 whitespace-nowrap",
1255
+ children
1256
+ }), /* @__PURE__ */ jsx(Select.ItemIndicator, {
1257
+ render: /* @__PURE__ */ jsx("span", { className: "pointer-events-none absolute right-2 flex size-4 items-center justify-center" }),
1258
+ children: /* @__PURE__ */ jsx(CheckIcon, { className: "pointer-events-none" })
1259
+ })]
1128
1260
  });
1129
1261
  }
1130
1262
  function SelectScrollUpButton({ className, ...props }) {
1131
- return /* @__PURE__ */ jsx(Select.ScrollUpButton, {
1263
+ return /* @__PURE__ */ jsx(Select.ScrollUpArrow, {
1132
1264
  "data-slot": "select-scroll-up-button",
1133
1265
  className: cn("flex cursor-default items-center justify-center py-1", className),
1134
1266
  ...props,
1135
- children: /* @__PURE__ */ jsx(ChevronUpIcon, { className: "size-4" })
1267
+ children: /* @__PURE__ */ jsx(ChevronUpIcon, {})
1136
1268
  });
1137
1269
  }
1138
1270
  function SelectScrollDownButton({ className, ...props }) {
1139
- return /* @__PURE__ */ jsx(Select.ScrollDownButton, {
1271
+ return /* @__PURE__ */ jsx(Select.ScrollDownArrow, {
1140
1272
  "data-slot": "select-scroll-down-button",
1141
1273
  className: cn("flex cursor-default items-center justify-center py-1", className),
1142
1274
  ...props,
1143
- children: /* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-4" })
1275
+ children: /* @__PURE__ */ jsx(ChevronDownIcon, {})
1144
1276
  });
1145
1277
  }
1146
1278
  //#endregion
@@ -1150,8 +1282,14 @@ const SurveyAssistantModelSelect = ({ disabled = false, modelId, models, onModel
1150
1282
  const selectedModelLabel = models.find((model) => model.id === modelId)?.label ?? modelId ?? t("input.defaultModel");
1151
1283
  if (models.length > 1) return /* @__PURE__ */ jsxs(Select$1, {
1152
1284
  disabled,
1153
- onValueChange: onModelChange,
1285
+ onValueChange: (value) => {
1286
+ if (value !== null) onModelChange(value);
1287
+ },
1154
1288
  value: modelId ?? "",
1289
+ items: [...models.map((model) => ({
1290
+ value: model.id,
1291
+ label: model.label
1292
+ }))],
1155
1293
  children: [/* @__PURE__ */ jsxs(SelectTrigger, {
1156
1294
  "aria-label": t("input.model"),
1157
1295
  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",
@@ -1161,13 +1299,13 @@ const SurveyAssistantModelSelect = ({ disabled = false, modelId, models, onModel
1161
1299
  }), /* @__PURE__ */ jsx(SelectContent, {
1162
1300
  align: "end",
1163
1301
  className: "min-w-52 rounded-lg border-border p-0 shadow-lg",
1164
- position: "popper",
1165
- children: models.map((model) => /* @__PURE__ */ jsx(SelectItem, {
1302
+ alignItemWithTrigger: false,
1303
+ children: /* @__PURE__ */ jsx(SelectGroup, { children: models.map((model) => /* @__PURE__ */ jsx(SelectItem, {
1166
1304
  className: "min-h-9 rounded-md px-2 py-2 text-xs focus:bg-muted",
1167
1305
  title: model.id,
1168
1306
  value: model.id,
1169
1307
  children: model.label
1170
- }, model.id))
1308
+ }, model.id)) })
1171
1309
  })]
1172
1310
  });
1173
1311
  return /* @__PURE__ */ jsxs("div", {
@@ -1221,8 +1359,9 @@ const SurveyAssistantPromptInput = ({ attachmentsDisabled = false, attachmentBut
1221
1359
  const formatOmittedLength = (length) => length.toLocaleString("en-US");
1222
1360
  const stringifyToolTechnicalDetails = (value) => JSON.stringify(value, function redactEmbeddedBinary(key, currentValue) {
1223
1361
  const parent = this && typeof this === "object" ? this : void 0;
1224
- if (key === "data" && typeof currentValue === "string" && parent?.type === "embedded" && parent.encoding === "base64") return `[embedded base64 omitted: ${formatOmittedLength(currentValue.length)} characters]`;
1225
- if (typeof currentValue === "string" && currentValue.length > 512 && /^data:[^;,]+;base64,/.test(currentValue)) return `[base64 data URL omitted: ${formatOmittedLength(currentValue.length)} characters]`;
1362
+ const marker = getSurveyAssistantEmbeddedBinaryMarker(currentValue, parent, key);
1363
+ if (marker?.redacted === "base64") return `[embedded base64 omitted: ${formatOmittedLength(marker.characters)} characters]`;
1364
+ if (marker?.redacted === "data-url") return `[base64 data URL omitted: ${formatOmittedLength(marker.characters)} characters]`;
1226
1365
  return currentValue;
1227
1366
  }, 2);
1228
1367
  //#endregion
@@ -1259,9 +1398,7 @@ const highlighterCache = /* @__PURE__ */ new Map();
1259
1398
  const tokensCache = /* @__PURE__ */ new Map();
1260
1399
  const subscribers = /* @__PURE__ */ new Map();
1261
1400
  const getTokensCacheKey = (code, language) => {
1262
- const start = code.slice(0, 100);
1263
- const end = code.length > 100 ? code.slice(-100) : "";
1264
- return `${language}:${code.length}:${start}:${end}`;
1401
+ return JSON.stringify([language, code]);
1265
1402
  };
1266
1403
  const getHighlighter = (language) => {
1267
1404
  const cached = highlighterCache.get(language);
@@ -1475,6 +1612,327 @@ const getSurveyAssistantInitialThreadLoadState = ({ error, hasSelectedThread, lo
1475
1612
  return null;
1476
1613
  };
1477
1614
  //#endregion
1615
+ //#region src/ui/assistant-error-presentation.ts
1616
+ const getAssistantErrorPresentation = ({ authorizationError, error, t }) => {
1617
+ const parsed = parseSurveyAssistantError(authorizationError ? {
1618
+ code: "request_failed",
1619
+ detail: authorizationError,
1620
+ retryable: false
1621
+ } : error);
1622
+ return {
1623
+ title: t(`errors.${parsed.code}.title`),
1624
+ description: parsed.detail ?? t("errors.fallbackDetail"),
1625
+ hint: t(`errors.${parsed.code}.hint`),
1626
+ providerMessage: parsed.providerMessage,
1627
+ providerRequestId: parsed.providerRequestId,
1628
+ retryable: isSurveyAssistantErrorRetryable(parsed),
1629
+ tone: parsed.code === "model_unavailable" || parsed.code === "rate_limited" ? "warning" : "error",
1630
+ upstreamStatus: parsed.upstreamStatus
1631
+ };
1632
+ };
1633
+ //#endregion
1634
+ //#region src/ui/internal/checkbox.tsx
1635
+ const Checkbox$1 = ({ className, ...props }) => /* @__PURE__ */ jsx(Checkbox.Root, {
1636
+ className: cn("relative flex size-4 shrink-0 items-center justify-center rounded border border-input outline-none transition-colors focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40 data-disabled:cursor-not-allowed data-disabled:opacity-50 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground", className),
1637
+ ...props,
1638
+ children: /* @__PURE__ */ jsx(Checkbox.Indicator, {
1639
+ className: "grid place-content-center text-current",
1640
+ children: /* @__PURE__ */ jsx(CheckIcon, { className: "size-3.5" })
1641
+ })
1642
+ });
1643
+ //#endregion
1644
+ //#region src/ui/internal/dialog.tsx
1645
+ const Dialog$1 = Dialog.Root;
1646
+ const DialogTitle = Dialog.Title;
1647
+ const DialogDescription = Dialog.Description;
1648
+ const DialogContent = ({ children, className, closeLabel, ...props }) => /* @__PURE__ */ jsx(Dialog.Portal, { children: /* @__PURE__ */ jsxs("div", {
1649
+ className: "case-survey-assistant case-survey-assistant-portal",
1650
+ children: [/* @__PURE__ */ jsx(Dialog.Backdrop, { className: "fixed inset-0 z-50 bg-black/20 backdrop-blur-[1px] transition-[opacity,transform,scale] ease-out motion-reduce:transition-none data-ending-style:opacity-0 data-starting-style:opacity-0" }), /* @__PURE__ */ jsxs(Dialog.Popup, {
1651
+ className: cn("fixed top-1/2 left-1/2 z-50 grid max-h-[min(42rem,calc(100vh-2rem))] w-[min(30rem,calc(100vw-2rem))] -translate-x-1/2 -translate-y-1/2 gap-4 overflow-y-auto rounded-xl border border-border bg-background p-4 text-foreground shadow-xl outline-none transition-[opacity,transform,scale] ease-out motion-reduce:transition-none data-ending-style:opacity-0 data-starting-style:opacity-0 data-ending-style:scale-95 data-starting-style:scale-95", className),
1652
+ ...props,
1653
+ children: [children, /* @__PURE__ */ jsx(Dialog.Close, {
1654
+ "aria-label": closeLabel,
1655
+ className: "absolute top-2.5 right-2.5 flex size-7 items-center justify-center rounded-md text-muted-foreground hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
1656
+ type: "button",
1657
+ children: /* @__PURE__ */ jsx(XIcon, { className: "size-4" })
1658
+ })]
1659
+ })]
1660
+ }) });
1661
+ //#endregion
1662
+ //#region src/ui/support-bundle-dialog.tsx
1663
+ const CATEGORIES = [
1664
+ "conversation",
1665
+ "survey",
1666
+ "attachments"
1667
+ ];
1668
+ const formatBytes = (bytes) => {
1669
+ if (bytes < 1024) return `${bytes} B`;
1670
+ if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)} KiB`;
1671
+ return `${(bytes / 1048576).toFixed(1)} MiB`;
1672
+ };
1673
+ const SurveyAssistantSupportBundleDialog = ({ config, error, fallbackFocusReturnRef, focusReturnRef, loading, onOpenChange, open, prepared }) => {
1674
+ const t = useSurveyAssistantTranslation();
1675
+ const [selection, setSelection] = useState({ ...SURVEY_ASSISTANT_DEFAULT_SUPPORT_BUNDLE_SELECTION });
1676
+ const [generated, setGenerated] = useState();
1677
+ const [generationError, setGenerationError] = useState();
1678
+ const [generating, setGenerating] = useState(false);
1679
+ const [supportDetailsCopied, setSupportDetailsCopied] = useState(false);
1680
+ const [supportDetailsCopyError, setSupportDetailsCopyError] = useState();
1681
+ const generationSequenceRef = useRef(0);
1682
+ const supportDetailsRef = useRef(null);
1683
+ useEffect(() => {
1684
+ generationSequenceRef.current += 1;
1685
+ if (open) setSelection({ ...SURVEY_ASSISTANT_DEFAULT_SUPPORT_BUNDLE_SELECTION });
1686
+ setGenerated(void 0);
1687
+ setGenerationError(void 0);
1688
+ setGenerating(false);
1689
+ setSupportDetailsCopied(false);
1690
+ setSupportDetailsCopyError(void 0);
1691
+ }, [open, prepared?.threadId]);
1692
+ const selectedBytes = useMemo(() => prepared ? estimateSurveyAssistantSupportBundleBytes(prepared, selection, config) : 0, [
1693
+ config,
1694
+ prepared,
1695
+ selection
1696
+ ]);
1697
+ const blocked = Boolean(prepared && selectedBytes > prepared.limits.bundleBytes);
1698
+ const warning = Boolean(prepared && selectedBytes > prepared.limits.emailWarningBytes);
1699
+ const generate = async () => {
1700
+ if (!prepared || blocked) return;
1701
+ const generationSequence = generationSequenceRef.current + 1;
1702
+ generationSequenceRef.current = generationSequence;
1703
+ setGenerating(true);
1704
+ setSupportDetailsCopied(false);
1705
+ setSupportDetailsCopyError(void 0);
1706
+ try {
1707
+ const bundle = await generateSurveyAssistantSupportBundle(prepared, selection, config);
1708
+ if (generationSequenceRef.current !== generationSequence) return;
1709
+ downloadSurveyAssistantSupportBundle(bundle);
1710
+ setGenerated(bundle);
1711
+ setGenerationError(void 0);
1712
+ setSupportDetailsCopied(false);
1713
+ setSupportDetailsCopyError(void 0);
1714
+ } catch {
1715
+ if (generationSequenceRef.current === generationSequence) setGenerationError(t("supportBundle.generationFailed"));
1716
+ } finally {
1717
+ if (generationSequenceRef.current === generationSequence) setGenerating(false);
1718
+ }
1719
+ };
1720
+ const updateSelection = (category, checked) => {
1721
+ generationSequenceRef.current += 1;
1722
+ setSelection((current) => ({
1723
+ ...current,
1724
+ [category]: checked
1725
+ }));
1726
+ setGenerated(void 0);
1727
+ setGenerationError(void 0);
1728
+ setGenerating(false);
1729
+ setSupportDetailsCopied(false);
1730
+ setSupportDetailsCopyError(void 0);
1731
+ };
1732
+ const mailto = generated && prepared && config ? createSurveyAssistantSupportMailto({
1733
+ bundle: generated,
1734
+ config,
1735
+ prepared
1736
+ }) : void 0;
1737
+ const supportDetails = generated && prepared ? createSurveyAssistantSupportDetails({
1738
+ bundle: generated,
1739
+ prepared
1740
+ }) : "";
1741
+ const copySupportDetails = async () => {
1742
+ if (!supportDetails) return;
1743
+ const copySequence = generationSequenceRef.current + 1;
1744
+ generationSequenceRef.current = copySequence;
1745
+ setSupportDetailsCopied(false);
1746
+ setSupportDetailsCopyError(void 0);
1747
+ try {
1748
+ if (navigator.clipboard?.writeText) await navigator.clipboard.writeText(supportDetails);
1749
+ else {
1750
+ if (!supportDetailsRef.current) throw new Error("Support details are not available.");
1751
+ supportDetailsRef.current.select();
1752
+ if (!document.execCommand("copy")) throw new Error("Copy was not available.");
1753
+ }
1754
+ if (generationSequenceRef.current !== copySequence) return;
1755
+ setSupportDetailsCopied(true);
1756
+ setSupportDetailsCopyError(void 0);
1757
+ } catch {
1758
+ if (generationSequenceRef.current !== copySequence) return;
1759
+ setSupportDetailsCopied(false);
1760
+ setSupportDetailsCopyError(t("supportBundle.copyDetailsFailed"));
1761
+ }
1762
+ };
1763
+ return /* @__PURE__ */ jsx(Dialog$1, {
1764
+ onOpenChange,
1765
+ open,
1766
+ children: /* @__PURE__ */ jsxs(DialogContent, {
1767
+ "aria-describedby": "survey-assistant-support-bundle-description",
1768
+ closeLabel: t("supportBundle.close"),
1769
+ finalFocus: () => {
1770
+ const target = focusReturnRef?.current?.isConnected ? focusReturnRef.current : fallbackFocusReturnRef?.current?.isConnected ? fallbackFocusReturnRef.current : null;
1771
+ if (!target) return;
1772
+ target.focus();
1773
+ return false;
1774
+ },
1775
+ children: [/* @__PURE__ */ jsxs("div", {
1776
+ className: "pr-8",
1777
+ children: [/* @__PURE__ */ jsx(DialogTitle, {
1778
+ className: "font-semibold text-base",
1779
+ children: t("supportBundle.title")
1780
+ }), /* @__PURE__ */ jsx(DialogDescription, {
1781
+ className: "mt-1 text-muted-foreground text-sm",
1782
+ id: "survey-assistant-support-bundle-description",
1783
+ children: t("supportBundle.description")
1784
+ })]
1785
+ }), loading ? /* @__PURE__ */ jsxs("div", {
1786
+ "aria-live": "polite",
1787
+ className: "flex min-h-28 items-center justify-center text-muted-foreground text-sm",
1788
+ role: "status",
1789
+ children: [/* @__PURE__ */ jsx(LoaderCircleIcon, { className: "mr-2 size-4 animate-spin" }), t("supportBundle.preparing")]
1790
+ }) : error ? /* @__PURE__ */ jsxs("div", {
1791
+ className: "rounded-md border border-destructive/20 bg-destructive/5 p-3 text-sm",
1792
+ children: [/* @__PURE__ */ jsx("div", {
1793
+ className: "font-medium",
1794
+ children: t("supportBundle.preparationFailed")
1795
+ }), /* @__PURE__ */ jsx("div", {
1796
+ className: "mt-1 text-muted-foreground",
1797
+ children: error
1798
+ })]
1799
+ }) : prepared ? /* @__PURE__ */ jsxs(Fragment, { children: [
1800
+ /* @__PURE__ */ jsxs("div", {
1801
+ className: "rounded-md border border-border bg-muted/30 p-3 text-xs",
1802
+ children: [/* @__PURE__ */ jsx("div", {
1803
+ className: "font-medium",
1804
+ children: t("supportBundle.core.title")
1805
+ }), /* @__PURE__ */ jsx("div", {
1806
+ className: "mt-1 text-muted-foreground",
1807
+ children: t("supportBundle.core.description")
1808
+ })]
1809
+ }),
1810
+ /* @__PURE__ */ jsxs("fieldset", {
1811
+ className: "grid gap-2",
1812
+ "aria-describedby": "support-bundle-sensitive-note",
1813
+ children: [/* @__PURE__ */ jsx("legend", {
1814
+ className: "mb-1 font-medium text-sm",
1815
+ children: t("supportBundle.chooseData")
1816
+ }), CATEGORIES.map((category) => {
1817
+ const id = `support-bundle-${category}`;
1818
+ return /* @__PURE__ */ jsxs("label", {
1819
+ className: "flex cursor-pointer items-start gap-3 rounded-md border border-border p-3 hover:bg-muted/30",
1820
+ htmlFor: id,
1821
+ children: [/* @__PURE__ */ jsx(Checkbox$1, {
1822
+ checked: selection[category],
1823
+ id,
1824
+ onCheckedChange: (checked) => updateSelection(category, checked === true)
1825
+ }), /* @__PURE__ */ jsxs("span", {
1826
+ className: "min-w-0 flex-1",
1827
+ children: [/* @__PURE__ */ jsxs("span", {
1828
+ className: "flex items-center justify-between gap-2 font-medium text-sm",
1829
+ children: [/* @__PURE__ */ jsx("span", { children: t(`supportBundle.categories.${category}.title`) }), /* @__PURE__ */ jsx("span", {
1830
+ className: "shrink-0 text-muted-foreground text-xs",
1831
+ children: formatBytes(prepared.categoryBytes[category])
1832
+ })]
1833
+ }), /* @__PURE__ */ jsx("span", {
1834
+ className: "mt-0.5 block text-muted-foreground text-xs",
1835
+ children: t(`supportBundle.categories.${category}.description`)
1836
+ })]
1837
+ })]
1838
+ }, category);
1839
+ })]
1840
+ }),
1841
+ /* @__PURE__ */ jsx("p", {
1842
+ className: "text-muted-foreground text-xs",
1843
+ id: "support-bundle-sensitive-note",
1844
+ children: t("supportBundle.sensitiveNote")
1845
+ }),
1846
+ /* @__PURE__ */ jsxs("div", {
1847
+ "aria-live": "polite",
1848
+ className: "rounded-md bg-muted px-3 py-2 text-xs text-foreground",
1849
+ role: "status",
1850
+ children: [
1851
+ /* @__PURE__ */ jsxs("div", {
1852
+ className: "flex justify-between gap-3 font-semibold",
1853
+ children: [/* @__PURE__ */ jsx("span", { children: t("supportBundle.estimatedSize") }), /* @__PURE__ */ jsx("span", { children: formatBytes(selectedBytes) })]
1854
+ }),
1855
+ warning && !blocked && /* @__PURE__ */ jsx("div", {
1856
+ className: "mt-1 text-amber-800 dark:text-amber-400",
1857
+ children: t("supportBundle.emailWarning")
1858
+ }),
1859
+ blocked && /* @__PURE__ */ jsx("div", {
1860
+ className: "mt-1 text-red-700 dark:text-red-400",
1861
+ children: t("supportBundle.tooLarge")
1862
+ }),
1863
+ prepared.unavailable.length > 0 && /* @__PURE__ */ jsx("div", {
1864
+ className: "mt-1",
1865
+ children: t(prepared.unavailable.length === 1 ? "supportBundle.unavailable" : "supportBundle.unavailablePlural", { count: prepared.unavailable.length })
1866
+ }),
1867
+ (generationError ?? error) && /* @__PURE__ */ jsx("div", {
1868
+ className: "mt-1 text-red-700 dark:text-red-400",
1869
+ children: generationError ?? error
1870
+ })
1871
+ ]
1872
+ }),
1873
+ generated ? /* @__PURE__ */ jsxs(Fragment, { children: [!mailto && /* @__PURE__ */ jsxs("div", {
1874
+ className: "grid gap-2 rounded-md border border-border p-3",
1875
+ children: [
1876
+ /* @__PURE__ */ jsx("label", {
1877
+ className: "font-medium text-xs",
1878
+ htmlFor: "support-bundle-details",
1879
+ children: t("supportBundle.supportDetails")
1880
+ }),
1881
+ /* @__PURE__ */ jsx("textarea", {
1882
+ className: "min-h-24 resize-y rounded-md border border-input bg-background p-2 font-mono text-xs",
1883
+ id: "support-bundle-details",
1884
+ readOnly: true,
1885
+ ref: supportDetailsRef,
1886
+ value: supportDetails
1887
+ }),
1888
+ /* @__PURE__ */ jsx("div", {
1889
+ className: "flex justify-end",
1890
+ children: /* @__PURE__ */ jsxs(Button$1, {
1891
+ disabled: generating,
1892
+ onClick: () => void copySupportDetails(),
1893
+ type: "button",
1894
+ variant: "secondary",
1895
+ children: [supportDetailsCopied ? /* @__PURE__ */ jsx(CheckIcon, {}) : /* @__PURE__ */ jsx(CopyIcon, {}), t(supportDetailsCopied ? "supportBundle.detailsCopied" : "supportBundle.copyDetails")]
1896
+ })
1897
+ }),
1898
+ supportDetailsCopyError && /* @__PURE__ */ jsx("div", {
1899
+ "aria-live": "polite",
1900
+ className: "text-destructive text-xs",
1901
+ role: "status",
1902
+ children: supportDetailsCopyError
1903
+ })
1904
+ ]
1905
+ }), /* @__PURE__ */ jsxs("div", {
1906
+ className: "flex flex-wrap justify-end gap-2",
1907
+ children: [mailto && /* @__PURE__ */ jsxs(Button$1, {
1908
+ render: /* @__PURE__ */ jsx("a", { href: mailto }),
1909
+ variant: "secondary",
1910
+ nativeButton: false,
1911
+ children: [/* @__PURE__ */ jsx(MailIcon, {}), t("supportBundle.openEmail")]
1912
+ }), /* @__PURE__ */ jsxs(Button$1, {
1913
+ disabled: generating,
1914
+ onClick: () => void generate(),
1915
+ type: "button",
1916
+ children: [generating ? /* @__PURE__ */ jsx(LoaderCircleIcon, { className: "animate-spin" }) : /* @__PURE__ */ jsx(DownloadIcon, {}), t("supportBundle.downloadAgain")]
1917
+ })]
1918
+ })] }) : /* @__PURE__ */ jsx("div", {
1919
+ className: "flex justify-end",
1920
+ children: /* @__PURE__ */ jsxs(Button$1, {
1921
+ disabled: blocked || generating,
1922
+ onClick: () => void generate(),
1923
+ type: "button",
1924
+ children: [generating ? /* @__PURE__ */ jsx(LoaderCircleIcon, { className: "animate-spin" }) : /* @__PURE__ */ jsx(DownloadIcon, {}), t("supportBundle.download")]
1925
+ })
1926
+ }),
1927
+ /* @__PURE__ */ jsx("p", {
1928
+ className: "text-muted-foreground text-xs",
1929
+ children: generated ? t("supportBundle.downloaded", { filename: generated.filename }) : t("supportBundle.localOnly")
1930
+ })
1931
+ ] }) : null]
1932
+ })
1933
+ });
1934
+ };
1935
+ //#endregion
1478
1936
  //#region src/ui/survey-assistant-panel.tsx
1479
1937
  const isRootSurveyItem = (item) => typeof item.config === "object" && item.config !== null && item.config.isRoot === true;
1480
1938
  const getAssistantEmptyStateContext = (controller) => {
@@ -1542,7 +2000,7 @@ const hasRenderableParts = (message) => {
1542
2000
  const getFilePartLabel = (part) => part.filename ?? part.mediaType;
1543
2001
  const getToolOutput = (part) => part.state === "output-available" ? part.output : void 0;
1544
2002
  const getToolErrorText = (part) => part.state === "output-error" ? part.errorText : void 0;
1545
- const localizedToolNames = new Set([
2003
+ const localizedToolNames = /* @__PURE__ */ new Set([
1546
2004
  "survey_inspect",
1547
2005
  "survey_change",
1548
2006
  "survey_expression",
@@ -1553,16 +2011,6 @@ const localizedToolNames = new Set([
1553
2011
  const getToolDisplayName = (t, toolName, phase = "complete") => localizedToolNames.has(toolName) ? t(`activity.tools.${toolName}.${phase}`) : t(phase === "active" ? "activity.genericActive" : "activity.genericComplete");
1554
2012
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1555
2013
  const unwrapOutput = unwrapSurveyAssistantToolOutput;
1556
- const getAssistantErrorPresentation = (error, t) => {
1557
- const parsed = parseSurveyAssistantError(error);
1558
- return {
1559
- title: t(`errors.${parsed.code}.title`),
1560
- description: parsed.detail ?? t("errors.fallbackDetail"),
1561
- hint: t(`errors.${parsed.code}.hint`),
1562
- retryable: parsed.retryable ?? (parsed.code === "rate_limited" || parsed.code === "request_failed"),
1563
- tone: parsed.code === "model_unavailable" || parsed.code === "rate_limited" ? "warning" : "error"
1564
- };
1565
- };
1566
2014
  const getChangeSet = parseSurveyChangeSet;
1567
2015
  const isChangeSet = (output) => {
1568
2016
  return getChangeSet(output) !== null;
@@ -1969,7 +2417,7 @@ const SurveyAssistantHeaderIdentity = ({ indicatorClassName, status, title }) =>
1969
2417
  })]
1970
2418
  })]
1971
2419
  });
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 }) => {
2420
+ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, diagnosticStore, embedded = false, focusPromptOnMount = false, hasEarlierMessages, initialLaunch, initialMessages, modelId, models, onInitialLaunchHandled, onModelChange, onFirstSubmit, onExportDetails, onLoadEarlierMessages, onTurnSettled, onApplyReceipts, onSettleApply, onReviewChangeSets, onTurnActiveChange, resourceId, surveyId, threadControls, threadId, threadTitle }) => {
1973
2421
  const t = useSurveyAssistantTranslation();
1974
2422
  const emptyStateContext = getAssistantEmptyStateContext(controller);
1975
2423
  const assistantQuickPrompts = getAssistantQuickPrompts(t, emptyStateContext);
@@ -1977,6 +2425,7 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1977
2425
  api,
1978
2426
  applyPolicy,
1979
2427
  controller,
2428
+ diagnosticStore,
1980
2429
  initialMessages,
1981
2430
  modelId,
1982
2431
  onApplyReceipts,
@@ -1989,16 +2438,22 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1989
2438
  });
1990
2439
  const hasSubmittedMessageRef = useRef((initialMessages?.length ?? 0) > 0);
1991
2440
  const showAssistantError = Boolean(authorizationError) || Boolean(error);
1992
- const assistantErrorPresentation = getAssistantErrorPresentation(authorizationError ?? error, t);
2441
+ const assistantErrorPresentation = getAssistantErrorPresentation({
2442
+ authorizationError,
2443
+ error,
2444
+ t
2445
+ });
1993
2446
  const lastMessage = messages.at(-1);
1994
2447
  const isAssistantTurnActive = isSurveyAssistantTurnActive({
1995
2448
  isStreaming,
1996
2449
  state
1997
2450
  });
1998
2451
  const promptTextareaRef = useRef(null);
2452
+ const exportDetailsButtonRef = useRef(null);
1999
2453
  const shouldRestorePromptFocusRef = useRef(false);
2000
2454
  const turnWasActiveRef = useRef(false);
2001
2455
  const [loadingEarlierMessages, setLoadingEarlierMessages] = useState(false);
2456
+ const [providerMessageExpanded, setProviderMessageExpanded] = useState(false);
2002
2457
  const loadEarlierMessages = useCallback(async () => {
2003
2458
  if (loadingEarlierMessages) return;
2004
2459
  setLoadingEarlierMessages(true);
@@ -2219,15 +2674,40 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
2219
2674
  className: "mt-0.5 wrap-break-word text-muted-foreground",
2220
2675
  children: assistantErrorPresentation.description
2221
2676
  }),
2677
+ assistantErrorPresentation.providerMessage && /* @__PURE__ */ jsxs("div", {
2678
+ className: "mt-1",
2679
+ children: [/* @__PURE__ */ jsx("p", {
2680
+ className: cn("wrap-break-word text-muted-foreground text-xs", !providerMessageExpanded && "line-clamp-3"),
2681
+ children: assistantErrorPresentation.providerMessage
2682
+ }), assistantErrorPresentation.providerMessage.length > 180 && /* @__PURE__ */ jsx("button", {
2683
+ className: "mt-0.5 font-medium text-foreground text-xs hover:underline",
2684
+ onClick: () => setProviderMessageExpanded((current) => !current),
2685
+ type: "button",
2686
+ children: t(providerMessageExpanded ? "errors.hideProviderDetails" : "errors.showProviderDetails")
2687
+ })]
2688
+ }),
2689
+ (assistantErrorPresentation.upstreamStatus || assistantErrorPresentation.providerRequestId) && /* @__PURE__ */ jsx("p", {
2690
+ className: "mt-1 text-[11px] text-muted-foreground/80",
2691
+ children: [assistantErrorPresentation.upstreamStatus ? t("errors.upstreamStatus", { status: assistantErrorPresentation.upstreamStatus }) : null, assistantErrorPresentation.providerRequestId ? t("errors.providerRequestId", { requestId: assistantErrorPresentation.providerRequestId }) : null].filter(Boolean).join(" · ")
2692
+ }),
2222
2693
  /* @__PURE__ */ jsx("p", {
2223
2694
  className: "mt-1 text-muted-foreground/80",
2224
2695
  children: assistantErrorPresentation.hint
2225
2696
  }),
2226
- assistantErrorPresentation.retryable && /* @__PURE__ */ jsx("button", {
2227
- className: "mt-1.5 font-medium text-foreground text-xs hover:underline",
2228
- onClick: retry,
2229
- type: "button",
2230
- children: t("errors.retry")
2697
+ (assistantErrorPresentation.retryable || onExportDetails) && /* @__PURE__ */ jsxs("div", {
2698
+ className: "mt-1.5 flex flex-wrap gap-x-3 gap-y-1",
2699
+ children: [assistantErrorPresentation.retryable && /* @__PURE__ */ jsx("button", {
2700
+ className: "font-medium text-foreground text-xs hover:underline",
2701
+ onClick: retry,
2702
+ type: "button",
2703
+ children: t("errors.retry")
2704
+ }), onExportDetails && /* @__PURE__ */ jsx("button", {
2705
+ className: "font-medium text-foreground text-xs hover:underline",
2706
+ onClick: () => onExportDetails(exportDetailsButtonRef.current),
2707
+ ref: exportDetailsButtonRef,
2708
+ type: "button",
2709
+ children: t("errors.exportDetails")
2710
+ })]
2231
2711
  })
2232
2712
  ]
2233
2713
  })]
@@ -2276,14 +2756,12 @@ const mergeThreads = (current, incoming) => {
2276
2756
  const mergeRecentThreadPage = (current, recent) => {
2277
2757
  if (recent.length === 0) return [];
2278
2758
  const oldestRecentUpdate = Math.min(...recent.map((thread) => Date.parse(thread.updatedAt)));
2279
- return mergeThreads(Number.isFinite(oldestRecentUpdate) ? current.filter((thread) => Date.parse(thread.updatedAt) <= oldestRecentUpdate) : current, recent);
2280
- };
2281
- const listThreadPage = async (client, cursor) => {
2282
- if (client.listPage) return client.listPage(cursor ? { cursor } : void 0);
2283
- return cursor ? { threads: [] } : { threads: await client.list() };
2759
+ const olderThreads = Number.isFinite(oldestRecentUpdate) ? current.filter((thread) => Date.parse(thread.updatedAt) <= oldestRecentUpdate) : current;
2760
+ return mergeThreads(olderThreads, recent);
2284
2761
  };
2285
- const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], onConfirmDeleteThread, onModelChange = () => {}, onTurnActiveChange: onAssistantTurnActiveChange, resourceId, selectedThreadStorageKey, surveyId, threadApi, threadClient: providedThreadClient, threadLauncher, ...sessionProps }) => {
2762
+ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], onConfirmDeleteThread, onModelChange = () => {}, onTurnActiveChange: onAssistantTurnActiveChange, resourceId, selectedThreadStorageKey, supportBundle, surveyId, threadApi, threadClient: providedThreadClient, threadLauncher, ...sessionProps }) => {
2286
2763
  const t = useSurveyAssistantTranslation();
2764
+ const supportBundleEnabled = isSurveyAssistantSupportBundleEnabled(supportBundle);
2287
2765
  const threadClient = useMemo(() => providedThreadClient ?? createSurveyAssistantThreadClient(threadApi), [providedThreadClient, threadApi]);
2288
2766
  const storageKey = selectedThreadStorageKey === false ? null : selectedThreadStorageKey ?? `${SELECTED_THREAD_STORAGE_KEY}:${encodeURIComponent(resourceId)}${surveyId ? `:${encodeURIComponent(surveyId)}` : ""}`;
2289
2767
  const [threads, setThreads] = useState([]);
@@ -2302,8 +2780,23 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2302
2780
  const [assistantTurnActive, setAssistantTurnActive] = useState(false);
2303
2781
  const [initialLaunch, setInitialLaunch] = useState();
2304
2782
  const [promptFocusThreadId, setPromptFocusThreadId] = useState();
2783
+ const [diagnosticStore] = useState(() => {
2784
+ try {
2785
+ return createSurveyAssistantDiagnosticStore(supportBundle?.limits);
2786
+ } catch {
2787
+ return createSurveyAssistantDiagnosticStore();
2788
+ }
2789
+ });
2790
+ const [supportBundleOpen, setSupportBundleOpen] = useState(false);
2791
+ const [supportBundleLoading, setSupportBundleLoading] = useState(false);
2792
+ const [supportBundleError, setSupportBundleError] = useState();
2793
+ const [preparedSupportBundle, setPreparedSupportBundle] = useState();
2305
2794
  const historyControlsRef = useRef(null);
2795
+ const historyButtonRef = useRef(null);
2306
2796
  const loadingAllThreadsRef = useRef(false);
2797
+ const supportBundleAbortRef = useRef(null);
2798
+ const supportBundleFocusReturnRef = useRef(null);
2799
+ const supportBundleThreadIdRef = useRef(void 0);
2307
2800
  const selectModel = useCallback((modelId) => {
2308
2801
  if (!models.some((model) => model.id === modelId)) return;
2309
2802
  onModelChange(modelId);
@@ -2358,7 +2851,7 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2358
2851
  ]);
2359
2852
  useEffect(() => {
2360
2853
  let cancelled = false;
2361
- listThreadPage(threadClient).then(async (page) => {
2854
+ Promise.resolve().then(() => threadClient.listPage()).then(async (page) => {
2362
2855
  if (cancelled) return;
2363
2856
  const availableThreads = page.threads;
2364
2857
  setThreads(availableThreads);
@@ -2401,7 +2894,7 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2401
2894
  if (!nextThreadCursor || loadingOlderThreads) return;
2402
2895
  setLoadingOlderThreads(true);
2403
2896
  try {
2404
- const page = await listThreadPage(threadClient, nextThreadCursor);
2897
+ const page = await threadClient.listPage({ cursor: nextThreadCursor });
2405
2898
  setThreads((current) => mergeThreads(current, page.threads));
2406
2899
  setNextThreadCursor(page.nextCursor);
2407
2900
  } catch (error) {
@@ -2438,7 +2931,7 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2438
2931
  try {
2439
2932
  let cursor = nextThreadCursor;
2440
2933
  while (cursor) {
2441
- const page = await listThreadPage(threadClient, cursor);
2934
+ const page = await threadClient.listPage({ cursor });
2442
2935
  setThreads((current) => mergeThreads(current, page.threads));
2443
2936
  cursor = page.nextCursor;
2444
2937
  setNextThreadCursor(cursor);
@@ -2529,19 +3022,79 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2529
3022
  renameThread,
2530
3023
  threads
2531
3024
  ]);
3025
+ const closeSupportBundle = useCallback(() => {
3026
+ supportBundleAbortRef.current?.abort();
3027
+ supportBundleAbortRef.current = null;
3028
+ setSupportBundleOpen(false);
3029
+ setSupportBundleLoading(false);
3030
+ setPreparedSupportBundle(void 0);
3031
+ }, []);
3032
+ const openSupportBundle = useCallback(async (focusReturnElement) => {
3033
+ if (!supportBundleEnabled || !selectedThread || assistantTurnActive) return;
3034
+ supportBundleFocusReturnRef.current = focusReturnElement ?? historyButtonRef.current;
3035
+ await commitEditingThread();
3036
+ setHistoryOpen(false);
3037
+ supportBundleAbortRef.current?.abort();
3038
+ const abortController = new AbortController();
3039
+ supportBundleAbortRef.current = abortController;
3040
+ setSupportBundleOpen(true);
3041
+ supportBundleThreadIdRef.current = selectedThread.id;
3042
+ setSupportBundleLoading(true);
3043
+ setSupportBundleError(void 0);
3044
+ setPreparedSupportBundle(void 0);
3045
+ try {
3046
+ const prepared = await prepareSurveyAssistantSupportBundle({
3047
+ config: supportBundle,
3048
+ controller: sessionProps.controller,
3049
+ diagnosticStore,
3050
+ resourceId,
3051
+ signal: abortController.signal,
3052
+ threadClient,
3053
+ threadId: selectedThread.id
3054
+ });
3055
+ if (abortController.signal.aborted || selectedThread.id !== prepared.threadId) return;
3056
+ setPreparedSupportBundle(prepared);
3057
+ } catch (error) {
3058
+ if (abortController.signal.aborted) return;
3059
+ setSupportBundleError(parseSurveyAssistantError(error).detail ?? t("supportBundle.preparationFailed"));
3060
+ } finally {
3061
+ if (supportBundleAbortRef.current === abortController) {
3062
+ supportBundleAbortRef.current = null;
3063
+ setSupportBundleLoading(false);
3064
+ }
3065
+ }
3066
+ }, [
3067
+ assistantTurnActive,
3068
+ commitEditingThread,
3069
+ diagnosticStore,
3070
+ resourceId,
3071
+ selectedThread,
3072
+ sessionProps.controller,
3073
+ supportBundle,
3074
+ supportBundleEnabled,
3075
+ t,
3076
+ threadClient
3077
+ ]);
3078
+ useEffect(() => () => {
3079
+ supportBundleAbortRef.current?.abort();
3080
+ }, []);
2532
3081
  const deleteThread = useCallback(async (thread) => {
2533
3082
  if (!(onConfirmDeleteThread ? await onConfirmDeleteThread(thread) : window.confirm(t("threads.deleteConfirm", { title: thread.title })))) return;
2534
3083
  try {
2535
3084
  await threadClient.delete(thread.id);
3085
+ diagnosticStore.deleteThread(thread.id);
2536
3086
  const remaining = threads.filter((item) => item.id !== thread.id);
2537
3087
  setThreads(remaining);
2538
- if (selectedThread?.id === thread.id) if (remaining[0]) await selectThread(remaining[0]);
2539
- else await createThread();
3088
+ if (selectedThread?.id === thread.id) {
3089
+ if (remaining[0]) await selectThread(remaining[0]);
3090
+ else await createThread();
3091
+ }
2540
3092
  } catch (error) {
2541
3093
  setThreadError(parseSurveyAssistantError(error).detail ?? t("errors.fallbackDetail"));
2542
3094
  }
2543
3095
  }, [
2544
3096
  createThread,
3097
+ diagnosticStore,
2545
3098
  onConfirmDeleteThread,
2546
3099
  selectThread,
2547
3100
  selectedThread,
@@ -2564,7 +3117,7 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2564
3117
  }, [initialLaunch]);
2565
3118
  const refreshThreadList = useCallback(async (reportError) => {
2566
3119
  try {
2567
- const page = await listThreadPage(threadClient);
3120
+ const page = await threadClient.listPage();
2568
3121
  setThreads((current) => mergeRecentThreadPage(current, page.threads));
2569
3122
  const current = page.threads.find((thread) => thread.id === selectedThread?.id);
2570
3123
  if (current && !editingThreadId) setSelectedThread((selected) => {
@@ -2628,6 +3181,14 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2628
3181
  promptFocusThreadId,
2629
3182
  selectedThread?.id
2630
3183
  ]);
3184
+ useEffect(() => {
3185
+ if (!supportBundleOpen || supportBundleThreadIdRef.current === selectedThread?.id) return;
3186
+ closeSupportBundle();
3187
+ }, [
3188
+ closeSupportBundle,
3189
+ selectedThread?.id,
3190
+ supportBundleOpen
3191
+ ]);
2631
3192
  useEffect(() => {
2632
3193
  const handleFocus = () => {
2633
3194
  refreshThreadList(false);
@@ -2699,6 +3260,7 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2699
3260
  className: "flex size-8 items-center justify-center rounded-lg text-muted-foreground hover:bg-muted hover:text-foreground disabled:pointer-events-none disabled:opacity-50",
2700
3261
  disabled: assistantTurnActive,
2701
3262
  onClick: toggleHistory,
3263
+ ref: historyButtonRef,
2702
3264
  title: t("threads.history"),
2703
3265
  type: "button",
2704
3266
  children: /* @__PURE__ */ jsx(HistoryIcon, { className: "size-4" })
@@ -2789,6 +3351,15 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2789
3351
  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")
2790
3352
  })
2791
3353
  ]
3354
+ }),
3355
+ supportBundleEnabled && /* @__PURE__ */ jsx("div", {
3356
+ className: "border-t border-border p-1",
3357
+ children: /* @__PURE__ */ jsxs("button", {
3358
+ className: "flex h-9 w-full items-center gap-2 rounded-md px-2 text-left text-muted-foreground text-xs hover:bg-muted hover:text-foreground",
3359
+ onClick: () => void openSupportBundle(historyButtonRef.current),
3360
+ type: "button",
3361
+ children: [/* @__PURE__ */ jsx(PackageOpenIcon, { className: "size-3.5" }), t("threads.exportSupportBundle")]
3362
+ })
2792
3363
  })
2793
3364
  ]
2794
3365
  })
@@ -2796,46 +3367,63 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2796
3367
  });
2797
3368
  return /* @__PURE__ */ jsxs("div", {
2798
3369
  className: "relative h-full min-h-0",
2799
- children: [/* @__PURE__ */ createElement(SurveyAssistantChatPanel, {
2800
- ...sessionProps,
2801
- embedded,
2802
- focusPromptOnMount: selectedThread.id === promptFocusThreadId,
2803
- hasEarlierMessages: Boolean(nextMessageCursor),
2804
- initialLaunch: initialLaunch?.input,
2805
- initialMessages,
2806
- key: selectedThread.id,
2807
- modelId,
2808
- models,
2809
- onInitialLaunchHandled: handleInitialLaunchHandled,
2810
- onModelChange: selectModel,
2811
- onFirstSubmit: handleFirstSubmit,
2812
- onLoadEarlierMessages: loadEarlierMessages,
2813
- onTurnSettled: handleTurnSettled,
2814
- onTurnActiveChange: handleTurnActiveChange,
2815
- resourceId,
2816
- surveyId,
2817
- threadControls,
2818
- threadId: selectedThread.id,
2819
- threadTitle: selectedThread.title
2820
- }), threadError && /* @__PURE__ */ jsxs("div", {
2821
- 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",
2822
- children: [
2823
- /* @__PURE__ */ jsx("div", {
2824
- className: "font-medium",
2825
- children: t("panel.controlsUnavailable")
2826
- }),
2827
- /* @__PURE__ */ jsx("div", {
2828
- className: "mt-0.5 text-muted-foreground",
2829
- children: threadError
2830
- }),
2831
- /* @__PURE__ */ jsx("button", {
2832
- className: "mt-1 font-medium hover:underline",
2833
- onClick: () => setThreadError(null),
2834
- type: "button",
2835
- children: t("errors.dismiss")
2836
- })
2837
- ]
2838
- })]
3370
+ children: [
3371
+ /* @__PURE__ */ createElement(SurveyAssistantChatPanel, {
3372
+ ...sessionProps,
3373
+ embedded,
3374
+ focusPromptOnMount: selectedThread.id === promptFocusThreadId,
3375
+ hasEarlierMessages: Boolean(nextMessageCursor),
3376
+ initialLaunch: initialLaunch?.input,
3377
+ initialMessages,
3378
+ key: selectedThread.id,
3379
+ modelId,
3380
+ models,
3381
+ diagnosticStore,
3382
+ onExportDetails: supportBundleEnabled ? (trigger) => void openSupportBundle(trigger) : void 0,
3383
+ onInitialLaunchHandled: handleInitialLaunchHandled,
3384
+ onModelChange: selectModel,
3385
+ onFirstSubmit: handleFirstSubmit,
3386
+ onLoadEarlierMessages: loadEarlierMessages,
3387
+ onTurnSettled: handleTurnSettled,
3388
+ onTurnActiveChange: handleTurnActiveChange,
3389
+ resourceId,
3390
+ surveyId,
3391
+ threadControls,
3392
+ threadId: selectedThread.id,
3393
+ threadTitle: selectedThread.title
3394
+ }),
3395
+ supportBundleEnabled && /* @__PURE__ */ jsx(SurveyAssistantSupportBundleDialog, {
3396
+ config: supportBundle,
3397
+ error: supportBundleError,
3398
+ fallbackFocusReturnRef: historyButtonRef,
3399
+ focusReturnRef: supportBundleFocusReturnRef,
3400
+ loading: supportBundleLoading,
3401
+ onOpenChange: (open) => {
3402
+ if (!open) closeSupportBundle();
3403
+ },
3404
+ open: supportBundleOpen,
3405
+ prepared: preparedSupportBundle
3406
+ }),
3407
+ threadError && /* @__PURE__ */ jsxs("div", {
3408
+ 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",
3409
+ children: [
3410
+ /* @__PURE__ */ jsx("div", {
3411
+ className: "font-medium",
3412
+ children: t("panel.controlsUnavailable")
3413
+ }),
3414
+ /* @__PURE__ */ jsx("div", {
3415
+ className: "mt-0.5 text-muted-foreground",
3416
+ children: threadError
3417
+ }),
3418
+ /* @__PURE__ */ jsx("button", {
3419
+ className: "mt-1 font-medium hover:underline",
3420
+ onClick: () => setThreadError(null),
3421
+ type: "button",
3422
+ children: t("errors.dismiss")
3423
+ })
3424
+ ]
3425
+ })
3426
+ ]
2839
3427
  });
2840
3428
  };
2841
3429
  const SurveyAssistantPanel = ({ language, localizations, ...props }) => /* @__PURE__ */ jsx("div", {