@case-framework/survey-assistant 0.5.0 → 0.7.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 (114) hide show
  1. package/README.md +9 -4
  2. package/dist/attachments-B0hg3vpT.mjs +11 -0
  3. package/dist/attachments-B0hg3vpT.mjs.map +1 -0
  4. package/dist/authoring-references-BJQ_1nMX.mjs +115 -0
  5. package/dist/authoring-references-BJQ_1nMX.mjs.map +1 -0
  6. package/dist/capabilities-1KNnNO1_.mjs +73 -0
  7. package/dist/capabilities-1KNnNO1_.mjs.map +1 -0
  8. package/dist/{capabilities-CnsVuJ1k.d.mts → capabilities-DVTfogjv.d.mts} +1 -1
  9. package/dist/{capabilities-CnsVuJ1k.d.mts.map → capabilities-DVTfogjv.d.mts.map} +1 -1
  10. package/dist/capabilities-default.d.mts +1 -1
  11. package/dist/capabilities-default.d.mts.map +1 -1
  12. package/dist/capabilities-default.mjs +47 -20
  13. package/dist/capabilities-default.mjs.map +1 -1
  14. package/dist/{constants-CFUWrRtl.mjs → constants-0UHGFcRJ.mjs} +1 -1
  15. package/dist/{constants-CFUWrRtl.mjs.map → constants-0UHGFcRJ.mjs.map} +1 -1
  16. package/dist/{constants-DbrJxxOh.d.mts → constants-Be91Gay9.d.mts} +4 -4
  17. package/dist/{constants-DbrJxxOh.d.mts.map → constants-Be91Gay9.d.mts.map} +1 -1
  18. package/dist/{controller-proxy-jWIPOZAv.mjs → controller-proxy-BH05e-C2.mjs} +21 -8
  19. package/dist/controller-proxy-BH05e-C2.mjs.map +1 -0
  20. package/dist/{controller-proxy-BdKF2yrq.d.mts → controller-proxy-Bd0ZHBPF.d.mts} +11 -11
  21. package/dist/controller-proxy-Bd0ZHBPF.d.mts.map +1 -0
  22. package/dist/{digest-Dyjch_sC.d.mts → digest-SJmoYVaK.d.mts} +2 -2
  23. package/dist/{digest-Dyjch_sC.d.mts.map → digest-SJmoYVaK.d.mts.map} +1 -1
  24. package/dist/digest.d.mts +1 -1
  25. package/dist/{engine-DcSdLRle.mjs → engine-Bo85Oyj0.mjs} +3129 -2435
  26. package/dist/engine-Bo85Oyj0.mjs.map +1 -0
  27. package/dist/engine.d.mts +3 -3
  28. package/dist/engine.mjs +3 -3
  29. package/dist/{fair-guidance-CEdJYccK.mjs → fair-guidance-CRYCRxQm.mjs} +1 -1
  30. package/dist/{fair-guidance-CEdJYccK.mjs.map → fair-guidance-CRYCRxQm.mjs.map} +1 -1
  31. package/dist/{index-Da_Hb9-1.d.mts → index-Tnqv-yKW.d.mts} +73 -67
  32. package/dist/index-Tnqv-yKW.d.mts.map +1 -0
  33. package/dist/index-l_EK4yK7.d.mts +404 -0
  34. package/dist/index-l_EK4yK7.d.mts.map +1 -0
  35. package/dist/{index-BApnIiCI.d.mts → index-nhrgbgO5.d.mts} +24 -5
  36. package/dist/index-nhrgbgO5.d.mts.map +1 -0
  37. package/dist/{index-DhNQKOwr.d.mts → index-x0qprHgK.d.mts} +19 -7
  38. package/dist/index-x0qprHgK.d.mts.map +1 -0
  39. package/dist/{lifecycle-DWv5CiSu.d.mts → lifecycle-Bx0aq_4S.d.mts} +8 -2
  40. package/dist/{lifecycle-DWv5CiSu.d.mts.map → lifecycle-Bx0aq_4S.d.mts.map} +1 -1
  41. package/dist/{memory-thread-repository-CvYZwni6.mjs → memory-thread-repository-KWHQWFEW.mjs} +45 -14
  42. package/dist/memory-thread-repository-KWHQWFEW.mjs.map +1 -0
  43. package/dist/{protocol-DJUP4gc0.mjs → protocol-BhzQbx81.mjs} +66 -16
  44. package/dist/protocol-BhzQbx81.mjs.map +1 -0
  45. package/dist/protocol.d.mts +4 -4
  46. package/dist/protocol.mjs +3 -2
  47. package/dist/react-B3ybjJ-X.mjs +1620 -0
  48. package/dist/react-B3ybjJ-X.mjs.map +1 -0
  49. package/dist/react-integration.d.mts +1 -1
  50. package/dist/react-integration.mjs +1 -1
  51. package/dist/react.d.mts +3 -3
  52. package/dist/react.mjs +3 -3
  53. package/dist/references/assistant-operations.md +45 -39
  54. package/dist/references/expressions.md +28 -0
  55. package/dist/references/item-types.md +1 -1
  56. package/dist/references/localization.md +5 -4
  57. package/dist/references/rich-text-content.md +60 -27
  58. package/dist/references/source-material-surveys.md +9 -2
  59. package/dist/references/survey-data-model.md +9 -1
  60. package/dist/{request-context-D1sC54ZA.mjs → request-context-CG4SWijt.mjs} +3 -3
  61. package/dist/{request-context-D1sC54ZA.mjs.map → request-context-CG4SWijt.mjs.map} +1 -1
  62. package/dist/{request-guardrails-h4t6u8mP.mjs → request-guardrails-TmgQtqp4.mjs} +5 -12
  63. package/dist/request-guardrails-TmgQtqp4.mjs.map +1 -0
  64. package/dist/server-agent.d.mts +19 -8
  65. package/dist/server-agent.d.mts.map +1 -1
  66. package/dist/server-agent.mjs +11 -13
  67. package/dist/server-agent.mjs.map +1 -1
  68. package/dist/server-runtime.d.mts +12 -6
  69. package/dist/server-runtime.d.mts.map +1 -1
  70. package/dist/server-runtime.mjs +9 -4
  71. package/dist/server-runtime.mjs.map +1 -1
  72. package/dist/server-tasks.d.mts +1 -1
  73. package/dist/server-tasks.mjs +5 -5
  74. package/dist/server-tools.d.mts +1 -1
  75. package/dist/server-tools.mjs +1 -1
  76. package/dist/server.d.mts +7 -7
  77. package/dist/server.mjs +6 -6
  78. package/dist/storage-postgres.d.mts +1 -1
  79. package/dist/storage-postgres.mjs +1 -1
  80. package/dist/{tasks-CCtxjhwh.d.mts → tasks-CYqrcaRO.d.mts} +16 -4
  81. package/dist/tasks-CYqrcaRO.d.mts.map +1 -0
  82. package/dist/{thread-documents-Dvo6XS7o.d.mts → thread-documents-rmd6nyX9.d.mts} +1 -1
  83. package/dist/{thread-documents-Dvo6XS7o.d.mts.map → thread-documents-rmd6nyX9.d.mts.map} +1 -1
  84. package/dist/{thread-handlers-Bt9cDo-F.d.mts → thread-handlers-DBAaU7BA.d.mts} +7 -5
  85. package/dist/thread-handlers-DBAaU7BA.d.mts.map +1 -0
  86. package/dist/{tools-3tq-XqI0.mjs → tools-BvSovehT.mjs} +433 -495
  87. package/dist/tools-BvSovehT.mjs.map +1 -0
  88. package/dist/ui.css +1 -1
  89. package/dist/ui.d.mts +5 -4
  90. package/dist/ui.d.mts.map +1 -1
  91. package/dist/ui.mjs +744 -131
  92. package/dist/ui.mjs.map +1 -1
  93. package/docs/integration.md +43 -3
  94. package/package.json +21 -19
  95. package/dist/authoring-references-yyYC4Ydi.mjs +0 -98
  96. package/dist/authoring-references-yyYC4Ydi.mjs.map +0 -1
  97. package/dist/controller-proxy-BdKF2yrq.d.mts.map +0 -1
  98. package/dist/controller-proxy-jWIPOZAv.mjs.map +0 -1
  99. package/dist/engine-DcSdLRle.mjs.map +0 -1
  100. package/dist/form-authoring-BQh40yDb.mjs +0 -155
  101. package/dist/form-authoring-BQh40yDb.mjs.map +0 -1
  102. package/dist/index-BApnIiCI.d.mts.map +0 -1
  103. package/dist/index-Da_Hb9-1.d.mts.map +0 -1
  104. package/dist/index-DhNQKOwr.d.mts.map +0 -1
  105. package/dist/index-dV6df0W3.d.mts +0 -224
  106. package/dist/index-dV6df0W3.d.mts.map +0 -1
  107. package/dist/memory-thread-repository-CvYZwni6.mjs.map +0 -1
  108. package/dist/protocol-DJUP4gc0.mjs.map +0 -1
  109. package/dist/react-CY6N7lVi.mjs +0 -768
  110. package/dist/react-CY6N7lVi.mjs.map +0 -1
  111. package/dist/request-guardrails-h4t6u8mP.mjs.map +0 -1
  112. package/dist/tasks-CCtxjhwh.d.mts.map +0 -1
  113. package/dist/thread-handlers-Bt9cDo-F.d.mts.map +0 -1
  114. package/dist/tools-3tq-XqI0.mjs.map +0 -1
package/dist/ui.mjs CHANGED
@@ -1,15 +1,16 @@
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 { 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";
2
+ import { Lt as getSurveyAssistantEmbeddedBinaryMarker, Mt as parseSurveyAssistantError, jt as isSurveyAssistantErrorRetryable, l as surveyAssistantTurnFinalSchema, n as SURVEY_ASSISTANT_TURN_FINAL_PART_TYPE } from "./protocol-BhzQbx81.mjs";
3
+ import { n as isSurveyAssistantAttachmentMediaType, t as SURVEY_ASSISTANT_ATTACHMENT_MEDIA_TYPES } from "./attachments-B0hg3vpT.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-B3ybjJ-X.mjs";
5
+ import { o as connectSurveyAssistantThreadLauncher } from "./controller-proxy-BH05e-C2.mjs";
5
6
  import { getToolName, isFileUIPart, isReasoningUIPart, isToolUIPart } from "ai";
6
7
  import { createContext, createElement, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
7
- import { Collapsible, Select, Slot } from "radix-ui";
8
+ import { Checkbox, Collapsible, Dialog, Select, Slot, Tooltip } from "radix-ui";
8
9
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
10
  import { clsx } from "clsx";
10
11
  import { twMerge } from "tailwind-merge";
11
12
  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, SearchIcon, SparklesIcon, SquareIcon, Trash2Icon, WandSparklesIcon, WrenchIcon, XCircleIcon, XIcon } from "lucide-react";
13
+ 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";
13
14
  import { motion } from "motion/react";
14
15
  import { createLocalizationTranslator, mergeLocalizationCatalogs } from "@case-framework/ui-localization";
15
16
  import { cva } from "class-variance-authority";
@@ -123,9 +124,11 @@ const surveyAssistantEnMessages = {
123
124
  },
124
125
  input: {
125
126
  placeholder: "Ask about the survey...",
126
- attachFiles: "Attach files",
127
- dropFiles: "Drop images or PDFs to attach",
128
- unsupportedFiles: "Unsupported file type: {filenames}",
127
+ attachFiles: "Attach a PDF, PNG, or JPG",
128
+ dropFiles: "Drop a PDF, PNG, or JPG to attach",
129
+ supportedFiles: "Supported: PDF, PNG, and JPG. Export Word documents as PDF.",
130
+ unsupportedFiles: "{filenames} can't be attached. Supported formats: PDF, PNG, and JPG.",
131
+ wordFilesUnsupported: "Word documents aren't supported. Export {filenames} as PDF, then attach the PDF.",
129
132
  removeFile: "Remove {filename}",
130
133
  send: "Send message",
131
134
  stop: "Stop response",
@@ -244,6 +247,11 @@ const surveyAssistantEnMessages = {
244
247
  dismiss: "Dismiss",
245
248
  dismissAssistant: "Dismiss assistant error",
246
249
  retry: "Try again",
250
+ exportDetails: "Export details",
251
+ showProviderDetails: "Show full provider message",
252
+ hideProviderDetails: "Hide provider message",
253
+ upstreamStatus: "Provider status {status}",
254
+ providerRequestId: "Request {requestId}",
247
255
  fallbackDetail: "The assistant hit an error on the previous turn.",
248
256
  model_unavailable: {
249
257
  title: "Model unavailable",
@@ -269,6 +277,7 @@ const surveyAssistantEnMessages = {
269
277
  threads: {
270
278
  newChat: "New chat",
271
279
  history: "Chat history",
280
+ exportSupportBundle: "Export support bundle",
272
281
  recent: "Recent chats",
273
282
  search: "Search chats",
274
283
  searching: "Searching older chats…",
@@ -282,6 +291,48 @@ const surveyAssistantEnMessages = {
282
291
  delete: "Delete {title}",
283
292
  deleteTitle: "Delete chat",
284
293
  deleteConfirm: "Delete \"{title}\" and its attachments?"
294
+ },
295
+ supportBundle: {
296
+ title: "Export support bundle",
297
+ description: "Choose which broad data categories to include. The ZIP is created and downloaded in this browser.",
298
+ close: "Close export dialog",
299
+ preparing: "Preparing diagnostic data…",
300
+ preparationFailed: "The support bundle could not be prepared",
301
+ generationFailed: "The support bundle could not be generated.",
302
+ chooseData: "Data to include",
303
+ core: {
304
+ title: "Core technical diagnostics (always included)",
305
+ description: "Request and turn identifiers, timing, status and error details, counters, and availability information."
306
+ },
307
+ categories: {
308
+ conversation: {
309
+ title: "Conversation and agent activity",
310
+ description: "Messages, visible reasoning, tool activity, and apply results."
311
+ },
312
+ survey: {
313
+ title: "Survey data",
314
+ description: "The submitted survey and a snapshot captured when this dialog opened."
315
+ },
316
+ attachments: {
317
+ title: "Attachments",
318
+ description: "Available source PDFs and images from this browser session."
319
+ }
320
+ },
321
+ sensitiveNote: "Conversation, reasoning, surveys, and attachments can contain sensitive research or authoring data. Deselect a category if you do not want to share it.",
322
+ estimatedSize: "Estimated ZIP size",
323
+ emailWarning: "This may be too large for some email providers.",
324
+ tooLarge: "The selection is too large. Deselect a category before downloading.",
325
+ unavailable: "{count} diagnostic component is unavailable or was truncated.",
326
+ unavailablePlural: "{count} diagnostic components are unavailable or were truncated.",
327
+ localOnly: "Nothing is sent automatically. You decide whether and how to share the downloaded ZIP.",
328
+ download: "Download support bundle",
329
+ downloadAgain: "Download again",
330
+ downloaded: "Downloaded {filename}. Attach it manually when contacting support.",
331
+ openEmail: "Open email draft",
332
+ supportDetails: "Support details to include with your request",
333
+ copyDetails: "Copy support details",
334
+ detailsCopied: "Support details copied",
335
+ copyDetailsFailed: "The support details could not be copied. Select and copy them manually."
285
336
  }
286
337
  };
287
338
  //#endregion
@@ -338,9 +389,11 @@ const surveyAssistantNlMessages = {
338
389
  },
339
390
  input: {
340
391
  placeholder: "Vraag iets over de survey...",
341
- attachFiles: "Bestanden toevoegen",
342
- dropFiles: "Sleep afbeeldingen of pdf's hierheen om ze toe te voegen",
343
- unsupportedFiles: "Niet-ondersteund bestandstype: {filenames}",
392
+ attachFiles: "Een pdf-, png- of jpg-bestand toevoegen",
393
+ dropFiles: "Sleep een pdf-, png- of jpg-bestand hierheen om het toe te voegen",
394
+ supportedFiles: "Ondersteund: pdf, png en jpg. Exporteer Word-documenten als pdf.",
395
+ unsupportedFiles: "{filenames} kan niet worden toegevoegd. Ondersteunde formaten: pdf, png en jpg.",
396
+ wordFilesUnsupported: "Word-documenten worden niet ondersteund. Exporteer {filenames} als pdf en voeg daarna de pdf toe.",
344
397
  removeFile: "{filename} verwijderen",
345
398
  send: "Bericht verzenden",
346
399
  stop: "Antwoord stoppen",
@@ -459,6 +512,11 @@ const surveyAssistantNlMessages = {
459
512
  dismiss: "Sluiten",
460
513
  dismissAssistant: "Assistentfout sluiten",
461
514
  retry: "Opnieuw proberen",
515
+ exportDetails: "Details exporteren",
516
+ showProviderDetails: "Volledig providerbericht tonen",
517
+ hideProviderDetails: "Providerbericht verbergen",
518
+ upstreamStatus: "Providerstatus {status}",
519
+ providerRequestId: "Verzoek {requestId}",
462
520
  fallbackDetail: "Er is tijdens de vorige beurt een fout opgetreden.",
463
521
  model_unavailable: {
464
522
  title: "Model niet beschikbaar",
@@ -484,6 +542,7 @@ const surveyAssistantNlMessages = {
484
542
  threads: {
485
543
  newChat: "Nieuwe chat",
486
544
  history: "Chatgeschiedenis",
545
+ exportSupportBundle: "Supportbundel exporteren",
487
546
  recent: "Recente chats",
488
547
  search: "Chats zoeken",
489
548
  searching: "Oudere chats zoeken…",
@@ -497,6 +556,48 @@ const surveyAssistantNlMessages = {
497
556
  delete: "{title} verwijderen",
498
557
  deleteTitle: "Chat verwijderen",
499
558
  deleteConfirm: "\"{title}\" en de bijlagen verwijderen?"
559
+ },
560
+ supportBundle: {
561
+ title: "Supportbundel exporteren",
562
+ description: "Kies welke brede gegevenscategorieën worden opgenomen. Het ZIP-bestand wordt in deze browser gemaakt en gedownload.",
563
+ close: "Exportvenster sluiten",
564
+ preparing: "Diagnostische gegevens voorbereiden…",
565
+ preparationFailed: "De supportbundel kon niet worden voorbereid",
566
+ generationFailed: "De supportbundel kon niet worden gemaakt.",
567
+ chooseData: "Op te nemen gegevens",
568
+ core: {
569
+ title: "Technische basisgegevens (altijd opgenomen)",
570
+ description: "Verzoek- en beurt-ID's, timing, status- en foutdetails, aantallen en beschikbaarheid."
571
+ },
572
+ categories: {
573
+ conversation: {
574
+ title: "Gesprek en agentactiviteit",
575
+ description: "Berichten, zichtbare redenering, hulpmiddelactiviteit en toepassingsresultaten."
576
+ },
577
+ survey: {
578
+ title: "Surveygegevens",
579
+ description: "De verzonden survey en een momentopname van toen dit venster werd geopend."
580
+ },
581
+ attachments: {
582
+ title: "Bijlagen",
583
+ description: "Beschikbare bron-pdf's en afbeeldingen uit deze browsersessie."
584
+ }
585
+ },
586
+ sensitiveNote: "Gesprek, redenering, surveys en bijlagen kunnen gevoelige onderzoeks- of auteursgegevens bevatten. Schakel een categorie uit als je die niet wilt delen.",
587
+ estimatedSize: "Geschatte ZIP-grootte",
588
+ emailWarning: "Dit bestand kan te groot zijn voor sommige e-mailproviders.",
589
+ tooLarge: "De selectie is te groot. Schakel een categorie uit voordat je downloadt.",
590
+ unavailable: "{count} diagnostisch onderdeel is niet beschikbaar of is ingekort.",
591
+ unavailablePlural: "{count} diagnostische onderdelen zijn niet beschikbaar of zijn ingekort.",
592
+ localOnly: "Er wordt niets automatisch verzonden. Je bepaalt zelf of en hoe je het gedownloade ZIP-bestand deelt.",
593
+ download: "Supportbundel downloaden",
594
+ downloadAgain: "Opnieuw downloaden",
595
+ downloaded: "{filename} is gedownload. Voeg het handmatig toe wanneer je contact opneemt met support.",
596
+ openEmail: "E-mailconcept openen",
597
+ supportDetails: "Supportgegevens voor je verzoek",
598
+ copyDetails: "Supportgegevens kopiëren",
599
+ detailsCopied: "Supportgegevens gekopieerd",
600
+ copyDetailsFailed: "De supportgegevens konden niet worden gekopieerd. Selecteer en kopieer ze handmatig."
500
601
  }
501
602
  };
502
603
  //#endregion
@@ -771,9 +872,39 @@ const MessageContent = ({ children, className, ...props }) => /* @__PURE__ */ js
771
872
  children
772
873
  });
773
874
  //#endregion
875
+ //#region src/ui/internal/tooltip.tsx
876
+ const TooltipProvider = Tooltip.Provider;
877
+ const Tooltip$1 = Tooltip.Root;
878
+ const TooltipTrigger = Tooltip.Trigger;
879
+ const TooltipContent = ({ className, sideOffset = 6, ...props }) => /* @__PURE__ */ jsx(Tooltip.Portal, { children: /* @__PURE__ */ jsx("div", {
880
+ className: "case-survey-assistant case-survey-assistant-portal",
881
+ children: /* @__PURE__ */ jsx(Tooltip.Content, {
882
+ 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),
883
+ sideOffset,
884
+ ...props
885
+ })
886
+ }) });
887
+ //#endregion
774
888
  //#region src/ui/internal/prompt-input.tsx
775
- const SUPPORTED_FILE_ACCEPT = "image/*,application/pdf";
776
- const isSupportedFile = (file) => file.type.startsWith("image/") || file.type === "application/pdf";
889
+ const SUPPORTED_FILE_ACCEPT = [
890
+ ".pdf",
891
+ ".png",
892
+ ".jpg",
893
+ ".jpeg",
894
+ ...SURVEY_ASSISTANT_ATTACHMENT_MEDIA_TYPES
895
+ ].join(",");
896
+ const WORD_DOCUMENT_MEDIA_TYPES = new Set([
897
+ "application/msword",
898
+ "application/vnd.ms-word",
899
+ "application/vnd.ms-word.document.macroenabled.12",
900
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
901
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.template"
902
+ ]);
903
+ const getUnsupportedAttachmentKind = ({ name, type }) => {
904
+ if (isSurveyAssistantAttachmentMediaType(type)) return null;
905
+ if (WORD_DOCUMENT_MEDIA_TYPES.has(type.toLowerCase()) || /\.(?:doc|docm|docx|dot|dotm|dotx)$/i.test(name)) return "word";
906
+ return "other";
907
+ };
777
908
  const formatFileSize = (size) => {
778
909
  if (size < 1024 * 1024) return `${Math.max(1, Math.round(size / 1024))} KB`;
779
910
  return `${(size / (1024 * 1024)).toFixed(size < 10 * 1024 * 1024 ? 1 : 0)} MB`;
@@ -801,7 +932,7 @@ const PromptInput = ({ attachmentsDisabled = false, children, className, onDragE
801
932
  const [uncontrolledValue, setUncontrolledValue] = useState("");
802
933
  const [files, setFiles] = useState([]);
803
934
  const [isDraggingFiles, setIsDraggingFiles] = useState(false);
804
- const [unsupportedFileNames, setUnsupportedFileNames] = useState([]);
935
+ const [unsupportedFiles, setUnsupportedFiles] = useState([]);
805
936
  const fileDragDepthRef = useRef(0);
806
937
  const t = useSurveyAssistantTranslation();
807
938
  const value = controlledValue ?? uncontrolledValue;
@@ -811,10 +942,16 @@ const PromptInput = ({ attachmentsDisabled = false, children, className, onDragE
811
942
  }, [controlledValue, onValueChange]);
812
943
  const addFiles = useCallback((fileList) => {
813
944
  const nextFiles = Array.from(fileList ?? []);
814
- const supportedFiles = nextFiles.filter(isSupportedFile);
815
- const unsupportedFiles = nextFiles.filter((file) => !isSupportedFile(file));
945
+ const supportedFiles = nextFiles.filter((file) => getUnsupportedAttachmentKind(file) === null);
946
+ const rejectedFiles = nextFiles.flatMap((file) => {
947
+ const kind = getUnsupportedAttachmentKind(file);
948
+ return kind ? [{
949
+ kind,
950
+ name: file.name
951
+ }] : [];
952
+ });
816
953
  setFiles((currentFiles) => [...currentFiles, ...supportedFiles]);
817
- setUnsupportedFileNames(unsupportedFiles.map((file) => file.name));
954
+ setUnsupportedFiles(rejectedFiles);
818
955
  }, []);
819
956
  const removeFile = useCallback((index) => {
820
957
  setFiles((currentFiles) => currentFiles.filter((_, fileIndex) => fileIndex !== index));
@@ -825,12 +962,12 @@ const PromptInput = ({ attachmentsDisabled = false, children, className, onDragE
825
962
  files,
826
963
  addFiles,
827
964
  removeFile,
828
- unsupportedFileNames
965
+ unsupportedFiles
829
966
  }), [
830
967
  addFiles,
831
968
  files,
832
969
  removeFile,
833
- unsupportedFileNames,
970
+ unsupportedFiles,
834
971
  value
835
972
  ]);
836
973
  const handleSubmit = useCallback(async (event) => {
@@ -843,7 +980,7 @@ const PromptInput = ({ attachmentsDisabled = false, children, className, onDragE
843
980
  }, event) === false) return;
844
981
  setValue("");
845
982
  setFiles([]);
846
- setUnsupportedFileNames([]);
983
+ setUnsupportedFiles([]);
847
984
  }, [
848
985
  files,
849
986
  onSubmit,
@@ -903,39 +1040,48 @@ const PromptInput = ({ attachmentsDisabled = false, children, className, onDragE
903
1040
  });
904
1041
  };
905
1042
  const PromptInputBody = ({ children, className, ...props }) => {
906
- const { files, removeFile, unsupportedFileNames } = usePromptInput();
1043
+ const { files, removeFile, unsupportedFiles } = usePromptInput();
907
1044
  const t = useSurveyAssistantTranslation();
1045
+ const unsupportedWordFileNames = unsupportedFiles.filter((file) => file.kind === "word").map((file) => file.name);
1046
+ const unsupportedOtherFileNames = unsupportedFiles.filter((file) => file.kind === "other").map((file) => file.name);
908
1047
  return /* @__PURE__ */ jsxs("div", {
909
1048
  className: cn("contents", className),
910
1049
  ...props,
911
- children: [children, (files.length > 0 || unsupportedFileNames.length > 0) && /* @__PURE__ */ jsxs("div", {
1050
+ children: [children, (files.length > 0 || unsupportedFiles.length > 0) && /* @__PURE__ */ jsxs("div", {
912
1051
  className: "flex flex-wrap gap-2 px-3 pb-2",
913
- children: [files.map((file, index) => /* @__PURE__ */ jsxs("div", {
914
- className: "inline-flex max-w-full items-center gap-2 rounded-md border border-border bg-muted px-2 py-1 text-xs",
915
- children: [
916
- /* @__PURE__ */ jsx(FileIcon, { className: "size-3.5 shrink-0 text-muted-foreground" }),
917
- /* @__PURE__ */ jsx("span", {
918
- className: "min-w-0 truncate",
919
- children: file.name
920
- }),
921
- /* @__PURE__ */ jsx("span", {
922
- className: "shrink-0 text-muted-foreground",
923
- children: formatFileSize(file.size)
924
- }),
925
- /* @__PURE__ */ jsxs("button", {
926
- className: "shrink-0 rounded-sm text-muted-foreground hover:text-foreground",
927
- onClick: () => removeFile(index),
928
- type: "button",
929
- children: [/* @__PURE__ */ jsx(XIcon, { className: "size-3.5" }), /* @__PURE__ */ jsx("span", {
930
- className: "sr-only",
931
- children: t("input.removeFile", { filename: file.name })
932
- })]
933
- })
934
- ]
935
- }, `${file.name}-${file.lastModified}-${index}`)), unsupportedFileNames.length > 0 && /* @__PURE__ */ jsx("div", {
936
- className: "text-destructive text-xs",
937
- children: t("input.unsupportedFiles", { filenames: unsupportedFileNames.join(", ") })
938
- })]
1052
+ children: [
1053
+ files.map((file, index) => /* @__PURE__ */ jsxs("div", {
1054
+ className: "inline-flex max-w-full items-center gap-2 rounded-md border border-border bg-muted px-2 py-1 text-xs",
1055
+ children: [
1056
+ /* @__PURE__ */ jsx(FileIcon, { className: "size-3.5 shrink-0 text-muted-foreground" }),
1057
+ /* @__PURE__ */ jsx("span", {
1058
+ className: "min-w-0 truncate",
1059
+ children: file.name
1060
+ }),
1061
+ /* @__PURE__ */ jsx("span", {
1062
+ className: "shrink-0 text-muted-foreground",
1063
+ children: formatFileSize(file.size)
1064
+ }),
1065
+ /* @__PURE__ */ jsxs("button", {
1066
+ className: "shrink-0 rounded-sm text-muted-foreground hover:text-foreground",
1067
+ onClick: () => removeFile(index),
1068
+ type: "button",
1069
+ children: [/* @__PURE__ */ jsx(XIcon, { className: "size-3.5" }), /* @__PURE__ */ jsx("span", {
1070
+ className: "sr-only",
1071
+ children: t("input.removeFile", { filename: file.name })
1072
+ })]
1073
+ })
1074
+ ]
1075
+ }, `${file.name}-${file.lastModified}-${index}`)),
1076
+ unsupportedWordFileNames.length > 0 && /* @__PURE__ */ jsx("div", {
1077
+ className: "text-destructive text-xs",
1078
+ children: t("input.wordFilesUnsupported", { filenames: unsupportedWordFileNames.join(", ") })
1079
+ }),
1080
+ unsupportedOtherFileNames.length > 0 && /* @__PURE__ */ jsx("div", {
1081
+ className: "text-destructive text-xs",
1082
+ children: t("input.unsupportedFiles", { filenames: unsupportedOtherFileNames.join(", ") })
1083
+ })
1084
+ ]
939
1085
  })]
940
1086
  });
941
1087
  };
@@ -964,28 +1110,38 @@ const PromptInputFooter = ({ className, ...props }) => /* @__PURE__ */ jsx("div"
964
1110
  const PromptInputAttachmentButton = ({ accept = SUPPORTED_FILE_ACCEPT, className, disabled, onChange, ...props }) => {
965
1111
  const { addFiles } = usePromptInput();
966
1112
  const t = useSurveyAssistantTranslation();
967
- return /* @__PURE__ */ jsxs("label", {
968
- className: cn("inline-flex size-8 cursor-pointer items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground", disabled && "pointer-events-none cursor-default opacity-50", className),
969
- children: [
970
- /* @__PURE__ */ jsx(PaperclipIcon, { className: "size-4" }),
971
- /* @__PURE__ */ jsx("span", {
972
- className: "sr-only",
973
- children: t("input.attachFiles")
974
- }),
975
- /* @__PURE__ */ jsx("input", {
976
- accept,
977
- className: "sr-only",
978
- disabled,
979
- multiple: true,
980
- onChange: (event) => {
981
- addFiles(event.currentTarget.files);
982
- onChange?.(event);
983
- event.currentTarget.value = "";
984
- },
985
- type: "file",
986
- ...props
1113
+ return /* @__PURE__ */ jsx(TooltipProvider, {
1114
+ delayDuration: 300,
1115
+ children: /* @__PURE__ */ jsxs(Tooltip$1, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
1116
+ asChild: true,
1117
+ children: /* @__PURE__ */ jsxs("label", {
1118
+ 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),
1119
+ children: [
1120
+ /* @__PURE__ */ jsx(PaperclipIcon, { className: "size-4" }),
1121
+ /* @__PURE__ */ jsx("span", {
1122
+ className: "sr-only",
1123
+ children: t("input.attachFiles")
1124
+ }),
1125
+ /* @__PURE__ */ jsx("input", {
1126
+ accept,
1127
+ className: "sr-only",
1128
+ disabled,
1129
+ multiple: true,
1130
+ onChange: (event) => {
1131
+ addFiles(event.currentTarget.files);
1132
+ onChange?.(event);
1133
+ event.currentTarget.value = "";
1134
+ },
1135
+ type: "file",
1136
+ ...props
1137
+ })
1138
+ ]
987
1139
  })
988
- ]
1140
+ }), /* @__PURE__ */ jsx(TooltipContent, {
1141
+ align: "start",
1142
+ side: "top",
1143
+ children: t("input.supportedFiles")
1144
+ })] })
989
1145
  });
990
1146
  };
991
1147
  const PromptInputSubmit = ({ className, status, onStop, onClick, ...props }) => {
@@ -1160,9 +1316,9 @@ const SurveyAssistantPromptInput = ({ attachmentsDisabled = false, attachmentBut
1160
1316
  //#region src/ui/internal/tool-technical-details.ts
1161
1317
  const formatOmittedLength = (length) => length.toLocaleString("en-US");
1162
1318
  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]`;
1319
+ const marker = getSurveyAssistantEmbeddedBinaryMarker(currentValue, this && typeof this === "object" ? this : void 0, key);
1320
+ if (marker?.redacted === "base64") return `[embedded base64 omitted: ${formatOmittedLength(marker.characters)} characters]`;
1321
+ if (marker?.redacted === "data-url") return `[base64 data URL omitted: ${formatOmittedLength(marker.characters)} characters]`;
1166
1322
  return currentValue;
1167
1323
  }, 2);
1168
1324
  //#endregion
@@ -1353,7 +1509,7 @@ const Response = ({ children, className, controls = false, plugins, ...props })
1353
1509
  code: plugins?.code ?? codeHighlighter
1354
1510
  }), [plugins]);
1355
1511
  return /* @__PURE__ */ jsx(Streamdown, {
1356
- className: cn("leading-6", "*:first:mt-0 *:last:mb-0", "[&_a]:text-primary [&_a]:underline [&_a]:underline-offset-2", "[&_blockquote]:my-3 [&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-3 [&_blockquote]:text-muted-foreground", "[&_h1]:mb-2 [&_h1]:mt-4 [&_h1]:text-base [&_h1]:font-semibold", "[&_h2]:mb-2 [&_h2]:mt-4 [&_h2]:text-sm [&_h2]:font-semibold", "[&_h3]:mb-1.5 [&_h3]:mt-3 [&_h3]:text-sm [&_h3]:font-semibold", "[&_li]:my-1 [&_ol]:my-2 [&_ol]:list-decimal [&_ol]:pl-5 [&_ul]:my-2 [&_ul]:list-disc [&_ul]:pl-5", "[&_p]:my-2", "**:data-[streamdown=code-block]:my-3 **:data-[streamdown=code-block]:gap-0 **:data-[streamdown=code-block]:overflow-hidden **:data-[streamdown=code-block]:rounded-lg **:data-[streamdown=code-block]:bg-muted/40 **:data-[streamdown=code-block]:p-0", "**:data-[streamdown=code-block-header]:px-2", "**:data-[streamdown=code-block-body]:rounded-none **:data-[streamdown=code-block-body]:border-0 **:data-[streamdown=code-block-body]:border-t **:data-[streamdown=code-block-body]:bg-background **:data-[streamdown=code-block-body]:p-3", "[&_strong]:font-semibold", "[&_table]:my-3 [&_table]:w-full [&_table]:border-collapse [&_td]:border [&_td]:border-border [&_td]:px-2 [&_td]:py-1 [&_th]:border [&_th]:border-border [&_th]:px-2 [&_th]:py-1 [&_th]:text-left [&_th]:font-semibold", className),
1512
+ className: cn("leading-6", "*:first:mt-0 *:last:mb-0", "[&_a]:text-primary [&_a]:underline [&_a]:underline-offset-2", "[&_blockquote]:my-3 [&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-3 [&_blockquote]:text-muted-foreground", "[&_h1]:mb-2 [&_h1]:mt-4 [&_h1]:text-base [&_h1]:font-semibold", "[&_h2]:mb-2 [&_h2]:mt-4 [&_h2]:text-sm [&_h2]:font-semibold", "[&_h3]:mb-1.5 [&_h3]:mt-3 [&_h3]:text-sm [&_h3]:font-semibold", "[&_li]:my-1 [&_ol]:my-2 [&_ol]:list-decimal [&_ol]:pl-5 [&_ul]:my-2 [&_ul]:list-disc [&_ul]:pl-5", "[&_p]:my-2", "**:data-[streamdown=inline-code]:text-[length:inherit] **:data-[streamdown=inline-code]:leading-[inherit]", "**:data-[streamdown=code-block]:my-3 **:data-[streamdown=code-block]:gap-0 **:data-[streamdown=code-block]:overflow-hidden **:data-[streamdown=code-block]:rounded-lg **:data-[streamdown=code-block]:bg-muted/40 **:data-[streamdown=code-block]:p-0", "**:data-[streamdown=code-block-header]:px-2", "**:data-[streamdown=code-block-body]:rounded-none **:data-[streamdown=code-block-body]:border-0 **:data-[streamdown=code-block-body]:border-t **:data-[streamdown=code-block-body]:bg-background **:data-[streamdown=code-block-body]:p-3", "[&_strong]:font-semibold", "[&_table]:my-3 [&_table]:w-full [&_table]:border-collapse [&_td]:border [&_td]:border-border [&_td]:px-2 [&_td]:py-1 [&_th]:border [&_th]:border-border [&_th]:px-2 [&_th]:py-1 [&_th]:text-left [&_th]:font-semibold", className),
1357
1513
  controls,
1358
1514
  plugins: responsePlugins,
1359
1515
  ...props,
@@ -1415,6 +1571,332 @@ const getSurveyAssistantInitialThreadLoadState = ({ error, hasSelectedThread, lo
1415
1571
  return null;
1416
1572
  };
1417
1573
  //#endregion
1574
+ //#region src/ui/assistant-error-presentation.ts
1575
+ const getAssistantErrorPresentation = ({ authorizationError, error, t }) => {
1576
+ const parsed = parseSurveyAssistantError(authorizationError ? {
1577
+ code: "request_failed",
1578
+ detail: authorizationError,
1579
+ retryable: false
1580
+ } : error);
1581
+ return {
1582
+ title: t(`errors.${parsed.code}.title`),
1583
+ description: parsed.detail ?? t("errors.fallbackDetail"),
1584
+ hint: t(`errors.${parsed.code}.hint`),
1585
+ providerMessage: parsed.providerMessage,
1586
+ providerRequestId: parsed.providerRequestId,
1587
+ retryable: isSurveyAssistantErrorRetryable(parsed),
1588
+ tone: parsed.code === "model_unavailable" || parsed.code === "rate_limited" ? "warning" : "error",
1589
+ upstreamStatus: parsed.upstreamStatus
1590
+ };
1591
+ };
1592
+ //#endregion
1593
+ //#region src/ui/internal/checkbox.tsx
1594
+ const Checkbox$1 = ({ className, ...props }) => /* @__PURE__ */ jsx(Checkbox.Root, {
1595
+ 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 disabled:cursor-not-allowed disabled:opacity-50 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground", className),
1596
+ ...props,
1597
+ children: /* @__PURE__ */ jsx(Checkbox.Indicator, {
1598
+ className: "grid place-content-center text-current",
1599
+ children: /* @__PURE__ */ jsx(CheckIcon, { className: "size-3.5" })
1600
+ })
1601
+ });
1602
+ //#endregion
1603
+ //#region src/ui/internal/dialog.tsx
1604
+ const Dialog$1 = Dialog.Root;
1605
+ const DialogTitle = Dialog.Title;
1606
+ const DialogDescription = Dialog.Description;
1607
+ const DialogContent = ({ children, className, closeLabel, ...props }) => /* @__PURE__ */ jsx(Dialog.Portal, { children: /* @__PURE__ */ jsxs("div", {
1608
+ className: "case-survey-assistant case-survey-assistant-portal",
1609
+ children: [/* @__PURE__ */ jsx(Dialog.Overlay, { className: "fixed inset-0 z-50 bg-black/20 backdrop-blur-[1px] data-[state=closed]:animate-out data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0" }), /* @__PURE__ */ jsxs(Dialog.Content, {
1610
+ 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 data-[state=closed]:animate-out data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95", className),
1611
+ ...props,
1612
+ children: [children, /* @__PURE__ */ jsx(Dialog.Close, {
1613
+ "aria-label": closeLabel,
1614
+ 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",
1615
+ type: "button",
1616
+ children: /* @__PURE__ */ jsx(XIcon, { className: "size-4" })
1617
+ })]
1618
+ })]
1619
+ }) });
1620
+ //#endregion
1621
+ //#region src/ui/support-bundle-dialog.tsx
1622
+ const CATEGORIES = [
1623
+ "conversation",
1624
+ "survey",
1625
+ "attachments"
1626
+ ];
1627
+ const formatBytes = (bytes) => {
1628
+ if (bytes < 1024) return `${bytes} B`;
1629
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KiB`;
1630
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
1631
+ };
1632
+ const SurveyAssistantSupportBundleDialog = ({ config, error, fallbackFocusReturnRef, focusReturnRef, loading, onOpenChange, open, prepared }) => {
1633
+ const t = useSurveyAssistantTranslation();
1634
+ const [selection, setSelection] = useState({ ...SURVEY_ASSISTANT_DEFAULT_SUPPORT_BUNDLE_SELECTION });
1635
+ const [generated, setGenerated] = useState();
1636
+ const [generationError, setGenerationError] = useState();
1637
+ const [generating, setGenerating] = useState(false);
1638
+ const [supportDetailsCopied, setSupportDetailsCopied] = useState(false);
1639
+ const [supportDetailsCopyError, setSupportDetailsCopyError] = useState();
1640
+ const generationSequenceRef = useRef(0);
1641
+ const supportDetailsRef = useRef(null);
1642
+ useEffect(() => {
1643
+ generationSequenceRef.current += 1;
1644
+ if (open) setSelection({ ...SURVEY_ASSISTANT_DEFAULT_SUPPORT_BUNDLE_SELECTION });
1645
+ setGenerated(void 0);
1646
+ setGenerationError(void 0);
1647
+ setGenerating(false);
1648
+ setSupportDetailsCopied(false);
1649
+ setSupportDetailsCopyError(void 0);
1650
+ }, [open, prepared?.threadId]);
1651
+ const selectedBytes = useMemo(() => prepared ? estimateSurveyAssistantSupportBundleBytes(prepared, selection, config) : 0, [
1652
+ config,
1653
+ prepared,
1654
+ selection
1655
+ ]);
1656
+ const blocked = Boolean(prepared && selectedBytes > prepared.limits.bundleBytes);
1657
+ const warning = Boolean(prepared && selectedBytes > prepared.limits.emailWarningBytes);
1658
+ const generate = async () => {
1659
+ if (!prepared || blocked) return;
1660
+ const generationSequence = generationSequenceRef.current + 1;
1661
+ generationSequenceRef.current = generationSequence;
1662
+ setGenerating(true);
1663
+ setSupportDetailsCopied(false);
1664
+ setSupportDetailsCopyError(void 0);
1665
+ try {
1666
+ const bundle = await generateSurveyAssistantSupportBundle(prepared, selection, config);
1667
+ if (generationSequenceRef.current !== generationSequence) return;
1668
+ downloadSurveyAssistantSupportBundle(bundle);
1669
+ setGenerated(bundle);
1670
+ setGenerationError(void 0);
1671
+ setSupportDetailsCopied(false);
1672
+ setSupportDetailsCopyError(void 0);
1673
+ } catch {
1674
+ if (generationSequenceRef.current === generationSequence) setGenerationError(t("supportBundle.generationFailed"));
1675
+ } finally {
1676
+ if (generationSequenceRef.current === generationSequence) setGenerating(false);
1677
+ }
1678
+ };
1679
+ const updateSelection = (category, checked) => {
1680
+ generationSequenceRef.current += 1;
1681
+ setSelection((current) => ({
1682
+ ...current,
1683
+ [category]: checked
1684
+ }));
1685
+ setGenerated(void 0);
1686
+ setGenerationError(void 0);
1687
+ setGenerating(false);
1688
+ setSupportDetailsCopied(false);
1689
+ setSupportDetailsCopyError(void 0);
1690
+ };
1691
+ const mailto = generated && prepared && config ? createSurveyAssistantSupportMailto({
1692
+ bundle: generated,
1693
+ config,
1694
+ prepared
1695
+ }) : void 0;
1696
+ const supportDetails = generated && prepared ? createSurveyAssistantSupportDetails({
1697
+ bundle: generated,
1698
+ prepared
1699
+ }) : "";
1700
+ const copySupportDetails = async () => {
1701
+ if (!supportDetails) return;
1702
+ const copySequence = generationSequenceRef.current + 1;
1703
+ generationSequenceRef.current = copySequence;
1704
+ setSupportDetailsCopied(false);
1705
+ setSupportDetailsCopyError(void 0);
1706
+ try {
1707
+ if (navigator.clipboard?.writeText) await navigator.clipboard.writeText(supportDetails);
1708
+ else {
1709
+ if (!supportDetailsRef.current) throw new Error("Support details are not available.");
1710
+ supportDetailsRef.current.select();
1711
+ if (!document.execCommand("copy")) throw new Error("Copy was not available.");
1712
+ }
1713
+ if (generationSequenceRef.current !== copySequence) return;
1714
+ setSupportDetailsCopied(true);
1715
+ setSupportDetailsCopyError(void 0);
1716
+ } catch {
1717
+ if (generationSequenceRef.current !== copySequence) return;
1718
+ setSupportDetailsCopied(false);
1719
+ setSupportDetailsCopyError(t("supportBundle.copyDetailsFailed"));
1720
+ }
1721
+ };
1722
+ return /* @__PURE__ */ jsx(Dialog$1, {
1723
+ onOpenChange,
1724
+ open,
1725
+ children: /* @__PURE__ */ jsxs(DialogContent, {
1726
+ "aria-describedby": "survey-assistant-support-bundle-description",
1727
+ closeLabel: t("supportBundle.close"),
1728
+ onCloseAutoFocus: (event) => {
1729
+ const target = focusReturnRef?.current?.isConnected ? focusReturnRef.current : fallbackFocusReturnRef?.current?.isConnected ? fallbackFocusReturnRef.current : null;
1730
+ if (!target) return;
1731
+ event.preventDefault();
1732
+ target.focus();
1733
+ },
1734
+ children: [/* @__PURE__ */ jsxs("div", {
1735
+ className: "pr-8",
1736
+ children: [/* @__PURE__ */ jsx(DialogTitle, {
1737
+ className: "font-semibold text-base",
1738
+ children: t("supportBundle.title")
1739
+ }), /* @__PURE__ */ jsx(DialogDescription, {
1740
+ className: "mt-1 text-muted-foreground text-sm",
1741
+ id: "survey-assistant-support-bundle-description",
1742
+ children: t("supportBundle.description")
1743
+ })]
1744
+ }), loading ? /* @__PURE__ */ jsxs("div", {
1745
+ "aria-live": "polite",
1746
+ className: "flex min-h-28 items-center justify-center text-muted-foreground text-sm",
1747
+ role: "status",
1748
+ children: [/* @__PURE__ */ jsx(LoaderCircleIcon, { className: "mr-2 size-4 animate-spin" }), t("supportBundle.preparing")]
1749
+ }) : error ? /* @__PURE__ */ jsxs("div", {
1750
+ className: "rounded-md border border-destructive/20 bg-destructive/5 p-3 text-sm",
1751
+ children: [/* @__PURE__ */ jsx("div", {
1752
+ className: "font-medium",
1753
+ children: t("supportBundle.preparationFailed")
1754
+ }), /* @__PURE__ */ jsx("div", {
1755
+ className: "mt-1 text-muted-foreground",
1756
+ children: error
1757
+ })]
1758
+ }) : prepared ? /* @__PURE__ */ jsxs(Fragment, { children: [
1759
+ /* @__PURE__ */ jsxs("div", {
1760
+ className: "rounded-md border border-border bg-muted/30 p-3 text-xs",
1761
+ children: [/* @__PURE__ */ jsx("div", {
1762
+ className: "font-medium",
1763
+ children: t("supportBundle.core.title")
1764
+ }), /* @__PURE__ */ jsx("div", {
1765
+ className: "mt-1 text-muted-foreground",
1766
+ children: t("supportBundle.core.description")
1767
+ })]
1768
+ }),
1769
+ /* @__PURE__ */ jsxs("fieldset", {
1770
+ className: "grid gap-2",
1771
+ "aria-describedby": "support-bundle-sensitive-note",
1772
+ children: [/* @__PURE__ */ jsx("legend", {
1773
+ className: "mb-1 font-medium text-sm",
1774
+ children: t("supportBundle.chooseData")
1775
+ }), CATEGORIES.map((category) => {
1776
+ const id = `support-bundle-${category}`;
1777
+ return /* @__PURE__ */ jsxs("label", {
1778
+ className: "flex cursor-pointer items-start gap-3 rounded-md border border-border p-3 hover:bg-muted/30",
1779
+ htmlFor: id,
1780
+ children: [/* @__PURE__ */ jsx(Checkbox$1, {
1781
+ checked: selection[category],
1782
+ id,
1783
+ onCheckedChange: (checked) => updateSelection(category, checked === true)
1784
+ }), /* @__PURE__ */ jsxs("span", {
1785
+ className: "min-w-0 flex-1",
1786
+ children: [/* @__PURE__ */ jsxs("span", {
1787
+ className: "flex items-center justify-between gap-2 font-medium text-sm",
1788
+ children: [/* @__PURE__ */ jsx("span", { children: t(`supportBundle.categories.${category}.title`) }), /* @__PURE__ */ jsx("span", {
1789
+ className: "shrink-0 text-muted-foreground text-xs",
1790
+ children: formatBytes(prepared.categoryBytes[category])
1791
+ })]
1792
+ }), /* @__PURE__ */ jsx("span", {
1793
+ className: "mt-0.5 block text-muted-foreground text-xs",
1794
+ children: t(`supportBundle.categories.${category}.description`)
1795
+ })]
1796
+ })]
1797
+ }, category);
1798
+ })]
1799
+ }),
1800
+ /* @__PURE__ */ jsx("p", {
1801
+ className: "text-muted-foreground text-xs",
1802
+ id: "support-bundle-sensitive-note",
1803
+ children: t("supportBundle.sensitiveNote")
1804
+ }),
1805
+ /* @__PURE__ */ jsxs("div", {
1806
+ "aria-live": "polite",
1807
+ className: "rounded-md bg-muted/40 px-3 py-2 text-xs",
1808
+ role: "status",
1809
+ children: [
1810
+ /* @__PURE__ */ jsxs("div", {
1811
+ className: "flex justify-between gap-3",
1812
+ children: [/* @__PURE__ */ jsx("span", { children: t("supportBundle.estimatedSize") }), /* @__PURE__ */ jsx("span", {
1813
+ className: "font-medium",
1814
+ children: formatBytes(selectedBytes)
1815
+ })]
1816
+ }),
1817
+ warning && !blocked && /* @__PURE__ */ jsx("div", {
1818
+ className: "mt-1 text-amber-700",
1819
+ children: t("supportBundle.emailWarning")
1820
+ }),
1821
+ blocked && /* @__PURE__ */ jsx("div", {
1822
+ className: "mt-1 text-destructive",
1823
+ children: t("supportBundle.tooLarge")
1824
+ }),
1825
+ prepared.unavailable.length > 0 && /* @__PURE__ */ jsx("div", {
1826
+ className: "mt-1 text-muted-foreground",
1827
+ children: t(prepared.unavailable.length === 1 ? "supportBundle.unavailable" : "supportBundle.unavailablePlural", { count: prepared.unavailable.length })
1828
+ }),
1829
+ (generationError ?? error) && /* @__PURE__ */ jsx("div", {
1830
+ className: "mt-1 text-destructive",
1831
+ children: generationError ?? error
1832
+ })
1833
+ ]
1834
+ }),
1835
+ generated ? /* @__PURE__ */ jsxs(Fragment, { children: [!mailto && /* @__PURE__ */ jsxs("div", {
1836
+ className: "grid gap-2 rounded-md border border-border p-3",
1837
+ children: [
1838
+ /* @__PURE__ */ jsx("label", {
1839
+ className: "font-medium text-xs",
1840
+ htmlFor: "support-bundle-details",
1841
+ children: t("supportBundle.supportDetails")
1842
+ }),
1843
+ /* @__PURE__ */ jsx("textarea", {
1844
+ className: "min-h-24 resize-y rounded-md border border-input bg-background p-2 font-mono text-xs",
1845
+ id: "support-bundle-details",
1846
+ readOnly: true,
1847
+ ref: supportDetailsRef,
1848
+ value: supportDetails
1849
+ }),
1850
+ /* @__PURE__ */ jsx("div", {
1851
+ className: "flex justify-end",
1852
+ children: /* @__PURE__ */ jsxs(Button, {
1853
+ disabled: generating,
1854
+ onClick: () => void copySupportDetails(),
1855
+ type: "button",
1856
+ variant: "secondary",
1857
+ children: [supportDetailsCopied ? /* @__PURE__ */ jsx(CheckIcon, {}) : /* @__PURE__ */ jsx(CopyIcon, {}), t(supportDetailsCopied ? "supportBundle.detailsCopied" : "supportBundle.copyDetails")]
1858
+ })
1859
+ }),
1860
+ supportDetailsCopyError && /* @__PURE__ */ jsx("div", {
1861
+ "aria-live": "polite",
1862
+ className: "text-destructive text-xs",
1863
+ role: "status",
1864
+ children: supportDetailsCopyError
1865
+ })
1866
+ ]
1867
+ }), /* @__PURE__ */ jsxs("div", {
1868
+ className: "flex flex-wrap justify-end gap-2",
1869
+ children: [mailto && /* @__PURE__ */ jsx(Button, {
1870
+ asChild: true,
1871
+ variant: "secondary",
1872
+ children: /* @__PURE__ */ jsxs("a", {
1873
+ href: mailto,
1874
+ children: [/* @__PURE__ */ jsx(MailIcon, {}), t("supportBundle.openEmail")]
1875
+ })
1876
+ }), /* @__PURE__ */ jsxs(Button, {
1877
+ disabled: generating,
1878
+ onClick: () => void generate(),
1879
+ type: "button",
1880
+ children: [generating ? /* @__PURE__ */ jsx(LoaderCircleIcon, { className: "animate-spin" }) : /* @__PURE__ */ jsx(DownloadIcon, {}), t("supportBundle.downloadAgain")]
1881
+ })]
1882
+ })] }) : /* @__PURE__ */ jsx("div", {
1883
+ className: "flex justify-end",
1884
+ children: /* @__PURE__ */ jsxs(Button, {
1885
+ disabled: blocked || generating,
1886
+ onClick: () => void generate(),
1887
+ type: "button",
1888
+ children: [generating ? /* @__PURE__ */ jsx(LoaderCircleIcon, { className: "animate-spin" }) : /* @__PURE__ */ jsx(DownloadIcon, {}), t("supportBundle.download")]
1889
+ })
1890
+ }),
1891
+ /* @__PURE__ */ jsx("p", {
1892
+ className: "text-muted-foreground text-xs",
1893
+ children: generated ? t("supportBundle.downloaded", { filename: generated.filename }) : t("supportBundle.localOnly")
1894
+ })
1895
+ ] }) : null]
1896
+ })
1897
+ });
1898
+ };
1899
+ //#endregion
1418
1900
  //#region src/ui/survey-assistant-panel.tsx
1419
1901
  const isRootSurveyItem = (item) => typeof item.config === "object" && item.config !== null && item.config.isRoot === true;
1420
1902
  const getAssistantEmptyStateContext = (controller) => {
@@ -1493,16 +1975,6 @@ const localizedToolNames = new Set([
1493
1975
  const getToolDisplayName = (t, toolName, phase = "complete") => localizedToolNames.has(toolName) ? t(`activity.tools.${toolName}.${phase}`) : t(phase === "active" ? "activity.genericActive" : "activity.genericComplete");
1494
1976
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1495
1977
  const unwrapOutput = unwrapSurveyAssistantToolOutput;
1496
- const getAssistantErrorPresentation = (error, t) => {
1497
- const parsed = parseSurveyAssistantError(error);
1498
- return {
1499
- title: t(`errors.${parsed.code}.title`),
1500
- description: parsed.detail ?? t("errors.fallbackDetail"),
1501
- hint: t(`errors.${parsed.code}.hint`),
1502
- retryable: parsed.retryable ?? (parsed.code === "rate_limited" || parsed.code === "request_failed"),
1503
- tone: parsed.code === "model_unavailable" || parsed.code === "rate_limited" ? "warning" : "error"
1504
- };
1505
- };
1506
1978
  const getChangeSet = parseSurveyChangeSet;
1507
1979
  const isChangeSet = (output) => {
1508
1980
  return getChangeSet(output) !== null;
@@ -1909,7 +2381,7 @@ const SurveyAssistantHeaderIdentity = ({ indicatorClassName, status, title }) =>
1909
2381
  })]
1910
2382
  })]
1911
2383
  });
1912
- 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 }) => {
2384
+ 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 }) => {
1913
2385
  const t = useSurveyAssistantTranslation();
1914
2386
  const emptyStateContext = getAssistantEmptyStateContext(controller);
1915
2387
  const assistantQuickPrompts = getAssistantQuickPrompts(t, emptyStateContext);
@@ -1917,6 +2389,7 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1917
2389
  api,
1918
2390
  applyPolicy,
1919
2391
  controller,
2392
+ diagnosticStore,
1920
2393
  initialMessages,
1921
2394
  modelId,
1922
2395
  onApplyReceipts,
@@ -1929,16 +2402,22 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1929
2402
  });
1930
2403
  const hasSubmittedMessageRef = useRef((initialMessages?.length ?? 0) > 0);
1931
2404
  const showAssistantError = Boolean(authorizationError) || Boolean(error);
1932
- const assistantErrorPresentation = getAssistantErrorPresentation(authorizationError ?? error, t);
2405
+ const assistantErrorPresentation = getAssistantErrorPresentation({
2406
+ authorizationError,
2407
+ error,
2408
+ t
2409
+ });
1933
2410
  const lastMessage = messages.at(-1);
1934
2411
  const isAssistantTurnActive = isSurveyAssistantTurnActive({
1935
2412
  isStreaming,
1936
2413
  state
1937
2414
  });
1938
2415
  const promptTextareaRef = useRef(null);
2416
+ const exportDetailsButtonRef = useRef(null);
1939
2417
  const shouldRestorePromptFocusRef = useRef(false);
1940
2418
  const turnWasActiveRef = useRef(false);
1941
2419
  const [loadingEarlierMessages, setLoadingEarlierMessages] = useState(false);
2420
+ const [providerMessageExpanded, setProviderMessageExpanded] = useState(false);
1942
2421
  const loadEarlierMessages = useCallback(async () => {
1943
2422
  if (loadingEarlierMessages) return;
1944
2423
  setLoadingEarlierMessages(true);
@@ -2159,15 +2638,40 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
2159
2638
  className: "mt-0.5 wrap-break-word text-muted-foreground",
2160
2639
  children: assistantErrorPresentation.description
2161
2640
  }),
2641
+ assistantErrorPresentation.providerMessage && /* @__PURE__ */ jsxs("div", {
2642
+ className: "mt-1",
2643
+ children: [/* @__PURE__ */ jsx("p", {
2644
+ className: cn("wrap-break-word text-muted-foreground text-xs", !providerMessageExpanded && "line-clamp-3"),
2645
+ children: assistantErrorPresentation.providerMessage
2646
+ }), assistantErrorPresentation.providerMessage.length > 180 && /* @__PURE__ */ jsx("button", {
2647
+ className: "mt-0.5 font-medium text-foreground text-xs hover:underline",
2648
+ onClick: () => setProviderMessageExpanded((current) => !current),
2649
+ type: "button",
2650
+ children: t(providerMessageExpanded ? "errors.hideProviderDetails" : "errors.showProviderDetails")
2651
+ })]
2652
+ }),
2653
+ (assistantErrorPresentation.upstreamStatus || assistantErrorPresentation.providerRequestId) && /* @__PURE__ */ jsx("p", {
2654
+ className: "mt-1 text-[11px] text-muted-foreground/80",
2655
+ children: [assistantErrorPresentation.upstreamStatus ? t("errors.upstreamStatus", { status: assistantErrorPresentation.upstreamStatus }) : null, assistantErrorPresentation.providerRequestId ? t("errors.providerRequestId", { requestId: assistantErrorPresentation.providerRequestId }) : null].filter(Boolean).join(" · ")
2656
+ }),
2162
2657
  /* @__PURE__ */ jsx("p", {
2163
2658
  className: "mt-1 text-muted-foreground/80",
2164
2659
  children: assistantErrorPresentation.hint
2165
2660
  }),
2166
- assistantErrorPresentation.retryable && /* @__PURE__ */ jsx("button", {
2167
- className: "mt-1.5 font-medium text-foreground text-xs hover:underline",
2168
- onClick: retry,
2169
- type: "button",
2170
- children: t("errors.retry")
2661
+ (assistantErrorPresentation.retryable || onExportDetails) && /* @__PURE__ */ jsxs("div", {
2662
+ className: "mt-1.5 flex flex-wrap gap-x-3 gap-y-1",
2663
+ children: [assistantErrorPresentation.retryable && /* @__PURE__ */ jsx("button", {
2664
+ className: "font-medium text-foreground text-xs hover:underline",
2665
+ onClick: retry,
2666
+ type: "button",
2667
+ children: t("errors.retry")
2668
+ }), onExportDetails && /* @__PURE__ */ jsx("button", {
2669
+ className: "font-medium text-foreground text-xs hover:underline",
2670
+ onClick: () => onExportDetails(exportDetailsButtonRef.current),
2671
+ ref: exportDetailsButtonRef,
2672
+ type: "button",
2673
+ children: t("errors.exportDetails")
2674
+ })]
2171
2675
  })
2172
2676
  ]
2173
2677
  })]
@@ -2222,8 +2726,9 @@ const listThreadPage = async (client, cursor) => {
2222
2726
  if (client.listPage) return client.listPage(cursor ? { cursor } : void 0);
2223
2727
  return cursor ? { threads: [] } : { threads: await client.list() };
2224
2728
  };
2225
- const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], onConfirmDeleteThread, onModelChange = () => {}, onTurnActiveChange: onAssistantTurnActiveChange, resourceId, selectedThreadStorageKey, surveyId, threadApi, threadClient: providedThreadClient, threadLauncher, ...sessionProps }) => {
2729
+ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], onConfirmDeleteThread, onModelChange = () => {}, onTurnActiveChange: onAssistantTurnActiveChange, resourceId, selectedThreadStorageKey, supportBundle, surveyId, threadApi, threadClient: providedThreadClient, threadLauncher, ...sessionProps }) => {
2226
2730
  const t = useSurveyAssistantTranslation();
2731
+ const supportBundleEnabled = isSurveyAssistantSupportBundleEnabled(supportBundle);
2227
2732
  const threadClient = useMemo(() => providedThreadClient ?? createSurveyAssistantThreadClient(threadApi), [providedThreadClient, threadApi]);
2228
2733
  const storageKey = selectedThreadStorageKey === false ? null : selectedThreadStorageKey ?? `${SELECTED_THREAD_STORAGE_KEY}:${encodeURIComponent(resourceId)}${surveyId ? `:${encodeURIComponent(surveyId)}` : ""}`;
2229
2734
  const [threads, setThreads] = useState([]);
@@ -2242,8 +2747,23 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2242
2747
  const [assistantTurnActive, setAssistantTurnActive] = useState(false);
2243
2748
  const [initialLaunch, setInitialLaunch] = useState();
2244
2749
  const [promptFocusThreadId, setPromptFocusThreadId] = useState();
2750
+ const [diagnosticStore] = useState(() => {
2751
+ try {
2752
+ return createSurveyAssistantDiagnosticStore(supportBundle?.limits);
2753
+ } catch {
2754
+ return createSurveyAssistantDiagnosticStore();
2755
+ }
2756
+ });
2757
+ const [supportBundleOpen, setSupportBundleOpen] = useState(false);
2758
+ const [supportBundleLoading, setSupportBundleLoading] = useState(false);
2759
+ const [supportBundleError, setSupportBundleError] = useState();
2760
+ const [preparedSupportBundle, setPreparedSupportBundle] = useState();
2245
2761
  const historyControlsRef = useRef(null);
2762
+ const historyButtonRef = useRef(null);
2246
2763
  const loadingAllThreadsRef = useRef(false);
2764
+ const supportBundleAbortRef = useRef(null);
2765
+ const supportBundleFocusReturnRef = useRef(null);
2766
+ const supportBundleThreadIdRef = useRef(void 0);
2247
2767
  const selectModel = useCallback((modelId) => {
2248
2768
  if (!models.some((model) => model.id === modelId)) return;
2249
2769
  onModelChange(modelId);
@@ -2469,10 +2989,67 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2469
2989
  renameThread,
2470
2990
  threads
2471
2991
  ]);
2992
+ const closeSupportBundle = useCallback(() => {
2993
+ supportBundleAbortRef.current?.abort();
2994
+ supportBundleAbortRef.current = null;
2995
+ setSupportBundleOpen(false);
2996
+ setSupportBundleLoading(false);
2997
+ setPreparedSupportBundle(void 0);
2998
+ }, []);
2999
+ const openSupportBundle = useCallback(async (focusReturnElement) => {
3000
+ if (!supportBundleEnabled || !selectedThread || assistantTurnActive) return;
3001
+ supportBundleFocusReturnRef.current = focusReturnElement ?? historyButtonRef.current;
3002
+ await commitEditingThread();
3003
+ setHistoryOpen(false);
3004
+ supportBundleAbortRef.current?.abort();
3005
+ const abortController = new AbortController();
3006
+ supportBundleAbortRef.current = abortController;
3007
+ setSupportBundleOpen(true);
3008
+ supportBundleThreadIdRef.current = selectedThread.id;
3009
+ setSupportBundleLoading(true);
3010
+ setSupportBundleError(void 0);
3011
+ setPreparedSupportBundle(void 0);
3012
+ try {
3013
+ const prepared = await prepareSurveyAssistantSupportBundle({
3014
+ config: supportBundle,
3015
+ controller: sessionProps.controller,
3016
+ diagnosticStore,
3017
+ resourceId,
3018
+ signal: abortController.signal,
3019
+ threadClient,
3020
+ threadId: selectedThread.id
3021
+ });
3022
+ if (abortController.signal.aborted || selectedThread.id !== prepared.threadId) return;
3023
+ setPreparedSupportBundle(prepared);
3024
+ } catch (error) {
3025
+ if (abortController.signal.aborted) return;
3026
+ setSupportBundleError(parseSurveyAssistantError(error).detail ?? t("supportBundle.preparationFailed"));
3027
+ } finally {
3028
+ if (supportBundleAbortRef.current === abortController) {
3029
+ supportBundleAbortRef.current = null;
3030
+ setSupportBundleLoading(false);
3031
+ }
3032
+ }
3033
+ }, [
3034
+ assistantTurnActive,
3035
+ commitEditingThread,
3036
+ diagnosticStore,
3037
+ resourceId,
3038
+ selectedThread,
3039
+ sessionProps.controller,
3040
+ supportBundle,
3041
+ supportBundleEnabled,
3042
+ t,
3043
+ threadClient
3044
+ ]);
3045
+ useEffect(() => () => {
3046
+ supportBundleAbortRef.current?.abort();
3047
+ }, []);
2472
3048
  const deleteThread = useCallback(async (thread) => {
2473
3049
  if (!(onConfirmDeleteThread ? await onConfirmDeleteThread(thread) : window.confirm(t("threads.deleteConfirm", { title: thread.title })))) return;
2474
3050
  try {
2475
3051
  await threadClient.delete(thread.id);
3052
+ diagnosticStore.deleteThread(thread.id);
2476
3053
  const remaining = threads.filter((item) => item.id !== thread.id);
2477
3054
  setThreads(remaining);
2478
3055
  if (selectedThread?.id === thread.id) if (remaining[0]) await selectThread(remaining[0]);
@@ -2482,6 +3059,7 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2482
3059
  }
2483
3060
  }, [
2484
3061
  createThread,
3062
+ diagnosticStore,
2485
3063
  onConfirmDeleteThread,
2486
3064
  selectThread,
2487
3065
  selectedThread,
@@ -2568,6 +3146,14 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2568
3146
  promptFocusThreadId,
2569
3147
  selectedThread?.id
2570
3148
  ]);
3149
+ useEffect(() => {
3150
+ if (!supportBundleOpen || supportBundleThreadIdRef.current === selectedThread?.id) return;
3151
+ closeSupportBundle();
3152
+ }, [
3153
+ closeSupportBundle,
3154
+ selectedThread?.id,
3155
+ supportBundleOpen
3156
+ ]);
2571
3157
  useEffect(() => {
2572
3158
  const handleFocus = () => {
2573
3159
  refreshThreadList(false);
@@ -2639,6 +3225,7 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2639
3225
  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",
2640
3226
  disabled: assistantTurnActive,
2641
3227
  onClick: toggleHistory,
3228
+ ref: historyButtonRef,
2642
3229
  title: t("threads.history"),
2643
3230
  type: "button",
2644
3231
  children: /* @__PURE__ */ jsx(HistoryIcon, { className: "size-4" })
@@ -2729,6 +3316,15 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2729
3316
  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")
2730
3317
  })
2731
3318
  ]
3319
+ }),
3320
+ supportBundleEnabled && /* @__PURE__ */ jsx("div", {
3321
+ className: "border-t border-border p-1",
3322
+ children: /* @__PURE__ */ jsxs("button", {
3323
+ 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",
3324
+ onClick: () => void openSupportBundle(historyButtonRef.current),
3325
+ type: "button",
3326
+ children: [/* @__PURE__ */ jsx(PackageOpenIcon, { className: "size-3.5" }), t("threads.exportSupportBundle")]
3327
+ })
2732
3328
  })
2733
3329
  ]
2734
3330
  })
@@ -2736,46 +3332,63 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2736
3332
  });
2737
3333
  return /* @__PURE__ */ jsxs("div", {
2738
3334
  className: "relative h-full min-h-0",
2739
- children: [/* @__PURE__ */ createElement(SurveyAssistantChatPanel, {
2740
- ...sessionProps,
2741
- embedded,
2742
- focusPromptOnMount: selectedThread.id === promptFocusThreadId,
2743
- hasEarlierMessages: Boolean(nextMessageCursor),
2744
- initialLaunch: initialLaunch?.input,
2745
- initialMessages,
2746
- key: selectedThread.id,
2747
- modelId,
2748
- models,
2749
- onInitialLaunchHandled: handleInitialLaunchHandled,
2750
- onModelChange: selectModel,
2751
- onFirstSubmit: handleFirstSubmit,
2752
- onLoadEarlierMessages: loadEarlierMessages,
2753
- onTurnSettled: handleTurnSettled,
2754
- onTurnActiveChange: handleTurnActiveChange,
2755
- resourceId,
2756
- surveyId,
2757
- threadControls,
2758
- threadId: selectedThread.id,
2759
- threadTitle: selectedThread.title
2760
- }), threadError && /* @__PURE__ */ jsxs("div", {
2761
- 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",
2762
- children: [
2763
- /* @__PURE__ */ jsx("div", {
2764
- className: "font-medium",
2765
- children: t("panel.controlsUnavailable")
2766
- }),
2767
- /* @__PURE__ */ jsx("div", {
2768
- className: "mt-0.5 text-muted-foreground",
2769
- children: threadError
2770
- }),
2771
- /* @__PURE__ */ jsx("button", {
2772
- className: "mt-1 font-medium hover:underline",
2773
- onClick: () => setThreadError(null),
2774
- type: "button",
2775
- children: t("errors.dismiss")
2776
- })
2777
- ]
2778
- })]
3335
+ children: [
3336
+ /* @__PURE__ */ createElement(SurveyAssistantChatPanel, {
3337
+ ...sessionProps,
3338
+ embedded,
3339
+ focusPromptOnMount: selectedThread.id === promptFocusThreadId,
3340
+ hasEarlierMessages: Boolean(nextMessageCursor),
3341
+ initialLaunch: initialLaunch?.input,
3342
+ initialMessages,
3343
+ key: selectedThread.id,
3344
+ modelId,
3345
+ models,
3346
+ diagnosticStore,
3347
+ onExportDetails: supportBundleEnabled ? (trigger) => void openSupportBundle(trigger) : void 0,
3348
+ onInitialLaunchHandled: handleInitialLaunchHandled,
3349
+ onModelChange: selectModel,
3350
+ onFirstSubmit: handleFirstSubmit,
3351
+ onLoadEarlierMessages: loadEarlierMessages,
3352
+ onTurnSettled: handleTurnSettled,
3353
+ onTurnActiveChange: handleTurnActiveChange,
3354
+ resourceId,
3355
+ surveyId,
3356
+ threadControls,
3357
+ threadId: selectedThread.id,
3358
+ threadTitle: selectedThread.title
3359
+ }),
3360
+ supportBundleEnabled && /* @__PURE__ */ jsx(SurveyAssistantSupportBundleDialog, {
3361
+ config: supportBundle,
3362
+ error: supportBundleError,
3363
+ fallbackFocusReturnRef: historyButtonRef,
3364
+ focusReturnRef: supportBundleFocusReturnRef,
3365
+ loading: supportBundleLoading,
3366
+ onOpenChange: (open) => {
3367
+ if (!open) closeSupportBundle();
3368
+ },
3369
+ open: supportBundleOpen,
3370
+ prepared: preparedSupportBundle
3371
+ }),
3372
+ threadError && /* @__PURE__ */ jsxs("div", {
3373
+ 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",
3374
+ children: [
3375
+ /* @__PURE__ */ jsx("div", {
3376
+ className: "font-medium",
3377
+ children: t("panel.controlsUnavailable")
3378
+ }),
3379
+ /* @__PURE__ */ jsx("div", {
3380
+ className: "mt-0.5 text-muted-foreground",
3381
+ children: threadError
3382
+ }),
3383
+ /* @__PURE__ */ jsx("button", {
3384
+ className: "mt-1 font-medium hover:underline",
3385
+ onClick: () => setThreadError(null),
3386
+ type: "button",
3387
+ children: t("errors.dismiss")
3388
+ })
3389
+ ]
3390
+ })
3391
+ ]
2779
3392
  });
2780
3393
  };
2781
3394
  const SurveyAssistantPanel = ({ language, localizations, ...props }) => /* @__PURE__ */ jsx("div", {