@case-framework/survey-assistant 0.6.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 (94) hide show
  1. package/README.md +9 -4
  2. package/dist/{authoring-references-CW38pYzK.mjs → authoring-references-BJQ_1nMX.mjs} +2 -2
  3. package/dist/authoring-references-BJQ_1nMX.mjs.map +1 -0
  4. package/dist/capabilities-1KNnNO1_.mjs +73 -0
  5. package/dist/capabilities-1KNnNO1_.mjs.map +1 -0
  6. package/dist/capabilities-default.d.mts +1 -1
  7. package/dist/capabilities-default.d.mts.map +1 -1
  8. package/dist/capabilities-default.mjs +22 -25
  9. package/dist/capabilities-default.mjs.map +1 -1
  10. package/dist/{constants-BMGTgfjv.d.mts → constants-Be91Gay9.d.mts} +3 -3
  11. package/dist/{constants-BMGTgfjv.d.mts.map → constants-Be91Gay9.d.mts.map} +1 -1
  12. package/dist/{controller-proxy-DMTqer6T.mjs → controller-proxy-BH05e-C2.mjs} +21 -8
  13. package/dist/controller-proxy-BH05e-C2.mjs.map +1 -0
  14. package/dist/{controller-proxy-mRvez2qw.d.mts → controller-proxy-Bd0ZHBPF.d.mts} +11 -11
  15. package/dist/controller-proxy-Bd0ZHBPF.d.mts.map +1 -0
  16. package/dist/{engine-CQwOAcwK.mjs → engine-Bo85Oyj0.mjs} +2985 -2347
  17. package/dist/engine-Bo85Oyj0.mjs.map +1 -0
  18. package/dist/engine.d.mts +2 -2
  19. package/dist/engine.mjs +3 -3
  20. package/dist/{index-D6nqkz0O.d.mts → index-Tnqv-yKW.d.mts} +56 -71
  21. package/dist/index-Tnqv-yKW.d.mts.map +1 -0
  22. package/dist/index-l_EK4yK7.d.mts +404 -0
  23. package/dist/index-l_EK4yK7.d.mts.map +1 -0
  24. package/dist/{index-DvTqML-0.d.mts → index-nhrgbgO5.d.mts} +19 -5
  25. package/dist/index-nhrgbgO5.d.mts.map +1 -0
  26. package/dist/{index-B0lxsAzx.d.mts → index-x0qprHgK.d.mts} +19 -7
  27. package/dist/index-x0qprHgK.d.mts.map +1 -0
  28. package/dist/{lifecycle-BmGAIUlv.d.mts → lifecycle-Bx0aq_4S.d.mts} +7 -1
  29. package/dist/{lifecycle-BmGAIUlv.d.mts.map → lifecycle-Bx0aq_4S.d.mts.map} +1 -1
  30. package/dist/{memory-thread-repository-CjFGVNZh.mjs → memory-thread-repository-KWHQWFEW.mjs} +43 -12
  31. package/dist/memory-thread-repository-KWHQWFEW.mjs.map +1 -0
  32. package/dist/{protocol-DJUP4gc0.mjs → protocol-BhzQbx81.mjs} +66 -16
  33. package/dist/protocol-BhzQbx81.mjs.map +1 -0
  34. package/dist/protocol.d.mts +3 -3
  35. package/dist/protocol.mjs +2 -2
  36. package/dist/react-B3ybjJ-X.mjs +1620 -0
  37. package/dist/react-B3ybjJ-X.mjs.map +1 -0
  38. package/dist/react-integration.d.mts +1 -1
  39. package/dist/react-integration.mjs +1 -1
  40. package/dist/react.d.mts +3 -3
  41. package/dist/react.mjs +3 -3
  42. package/dist/references/assistant-operations.md +45 -39
  43. package/dist/references/expressions.md +28 -0
  44. package/dist/references/item-types.md +1 -1
  45. package/dist/references/localization.md +5 -4
  46. package/dist/references/rich-text-content.md +60 -27
  47. package/dist/references/source-material-surveys.md +9 -2
  48. package/dist/references/survey-data-model.md +9 -1
  49. package/dist/{request-context-BjTHNcDd.mjs → request-context-CG4SWijt.mjs} +3 -3
  50. package/dist/{request-context-BjTHNcDd.mjs.map → request-context-CG4SWijt.mjs.map} +1 -1
  51. package/dist/server-agent.d.mts +19 -8
  52. package/dist/server-agent.d.mts.map +1 -1
  53. package/dist/server-agent.mjs +7 -11
  54. package/dist/server-agent.mjs.map +1 -1
  55. package/dist/server-runtime.d.mts +19 -13
  56. package/dist/server-runtime.d.mts.map +1 -1
  57. package/dist/server-runtime.mjs +7 -2
  58. package/dist/server-runtime.mjs.map +1 -1
  59. package/dist/server-tasks.d.mts +1 -1
  60. package/dist/server-tasks.mjs +2 -2
  61. package/dist/server-tools.d.mts +1 -1
  62. package/dist/server-tools.mjs +1 -1
  63. package/dist/server.d.mts +5 -5
  64. package/dist/server.mjs +3 -3
  65. package/dist/{tasks-DxzD25Zc.d.mts → tasks-CYqrcaRO.d.mts} +4 -4
  66. package/dist/{tasks-DxzD25Zc.d.mts.map → tasks-CYqrcaRO.d.mts.map} +1 -1
  67. package/dist/{thread-handlers-AbQbt4sX.d.mts → thread-handlers-DBAaU7BA.d.mts} +6 -4
  68. package/dist/thread-handlers-DBAaU7BA.d.mts.map +1 -0
  69. package/dist/{tools-CFh35R4d.mjs → tools-BvSovehT.mjs} +431 -495
  70. package/dist/tools-BvSovehT.mjs.map +1 -0
  71. package/dist/ui.css +1 -1
  72. package/dist/ui.d.mts +5 -4
  73. package/dist/ui.d.mts.map +1 -1
  74. package/dist/ui.mjs +619 -66
  75. package/dist/ui.mjs.map +1 -1
  76. package/docs/integration.md +40 -0
  77. package/package.json +21 -19
  78. package/dist/authoring-references-CW38pYzK.mjs.map +0 -1
  79. package/dist/controller-proxy-DMTqer6T.mjs.map +0 -1
  80. package/dist/controller-proxy-mRvez2qw.d.mts.map +0 -1
  81. package/dist/engine-CQwOAcwK.mjs.map +0 -1
  82. package/dist/form-authoring-kQDgC5oz.mjs +0 -155
  83. package/dist/form-authoring-kQDgC5oz.mjs.map +0 -1
  84. package/dist/index-B0lxsAzx.d.mts.map +0 -1
  85. package/dist/index-BS-Xjk1h.d.mts +0 -224
  86. package/dist/index-BS-Xjk1h.d.mts.map +0 -1
  87. package/dist/index-D6nqkz0O.d.mts.map +0 -1
  88. package/dist/index-DvTqML-0.d.mts.map +0 -1
  89. package/dist/memory-thread-repository-CjFGVNZh.mjs.map +0 -1
  90. package/dist/protocol-DJUP4gc0.mjs.map +0 -1
  91. package/dist/react-Dqlmasrs.mjs +0 -768
  92. package/dist/react-Dqlmasrs.mjs.map +0 -1
  93. package/dist/thread-handlers-AbQbt4sX.d.mts.map +0 -1
  94. package/dist/tools-CFh35R4d.mjs.map +0 -1
package/dist/ui.mjs CHANGED
@@ -1,16 +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";
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
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";
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";
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 { Checkbox, Collapsible, Dialog, Select, Slot, Tooltip } from "radix-ui";
9
9
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
10
10
  import { clsx } from "clsx";
11
11
  import { twMerge } from "tailwind-merge";
12
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";
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";
14
14
  import { motion } from "motion/react";
15
15
  import { createLocalizationTranslator, mergeLocalizationCatalogs } from "@case-framework/ui-localization";
16
16
  import { cva } from "class-variance-authority";
@@ -247,6 +247,11 @@ const surveyAssistantEnMessages = {
247
247
  dismiss: "Dismiss",
248
248
  dismissAssistant: "Dismiss assistant error",
249
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}",
250
255
  fallbackDetail: "The assistant hit an error on the previous turn.",
251
256
  model_unavailable: {
252
257
  title: "Model unavailable",
@@ -272,6 +277,7 @@ const surveyAssistantEnMessages = {
272
277
  threads: {
273
278
  newChat: "New chat",
274
279
  history: "Chat history",
280
+ exportSupportBundle: "Export support bundle",
275
281
  recent: "Recent chats",
276
282
  search: "Search chats",
277
283
  searching: "Searching older chats…",
@@ -285,6 +291,48 @@ const surveyAssistantEnMessages = {
285
291
  delete: "Delete {title}",
286
292
  deleteTitle: "Delete chat",
287
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."
288
336
  }
289
337
  };
290
338
  //#endregion
@@ -464,6 +512,11 @@ const surveyAssistantNlMessages = {
464
512
  dismiss: "Sluiten",
465
513
  dismissAssistant: "Assistentfout sluiten",
466
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}",
467
520
  fallbackDetail: "Er is tijdens de vorige beurt een fout opgetreden.",
468
521
  model_unavailable: {
469
522
  title: "Model niet beschikbaar",
@@ -489,6 +542,7 @@ const surveyAssistantNlMessages = {
489
542
  threads: {
490
543
  newChat: "Nieuwe chat",
491
544
  history: "Chatgeschiedenis",
545
+ exportSupportBundle: "Supportbundel exporteren",
492
546
  recent: "Recente chats",
493
547
  search: "Chats zoeken",
494
548
  searching: "Oudere chats zoeken…",
@@ -502,6 +556,48 @@ const surveyAssistantNlMessages = {
502
556
  delete: "{title} verwijderen",
503
557
  deleteTitle: "Chat verwijderen",
504
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."
505
601
  }
506
602
  };
507
603
  //#endregion
@@ -1220,9 +1316,9 @@ const SurveyAssistantPromptInput = ({ attachmentsDisabled = false, attachmentBut
1220
1316
  //#region src/ui/internal/tool-technical-details.ts
1221
1317
  const formatOmittedLength = (length) => length.toLocaleString("en-US");
1222
1318
  const stringifyToolTechnicalDetails = (value) => JSON.stringify(value, function redactEmbeddedBinary(key, currentValue) {
1223
- 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]`;
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]`;
1226
1322
  return currentValue;
1227
1323
  }, 2);
1228
1324
  //#endregion
@@ -1475,6 +1571,332 @@ const getSurveyAssistantInitialThreadLoadState = ({ error, hasSelectedThread, lo
1475
1571
  return null;
1476
1572
  };
1477
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
1478
1900
  //#region src/ui/survey-assistant-panel.tsx
1479
1901
  const isRootSurveyItem = (item) => typeof item.config === "object" && item.config !== null && item.config.isRoot === true;
1480
1902
  const getAssistantEmptyStateContext = (controller) => {
@@ -1553,16 +1975,6 @@ const localizedToolNames = new Set([
1553
1975
  const getToolDisplayName = (t, toolName, phase = "complete") => localizedToolNames.has(toolName) ? t(`activity.tools.${toolName}.${phase}`) : t(phase === "active" ? "activity.genericActive" : "activity.genericComplete");
1554
1976
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1555
1977
  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
1978
  const getChangeSet = parseSurveyChangeSet;
1567
1979
  const isChangeSet = (output) => {
1568
1980
  return getChangeSet(output) !== null;
@@ -1969,7 +2381,7 @@ const SurveyAssistantHeaderIdentity = ({ indicatorClassName, status, title }) =>
1969
2381
  })]
1970
2382
  })]
1971
2383
  });
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 }) => {
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 }) => {
1973
2385
  const t = useSurveyAssistantTranslation();
1974
2386
  const emptyStateContext = getAssistantEmptyStateContext(controller);
1975
2387
  const assistantQuickPrompts = getAssistantQuickPrompts(t, emptyStateContext);
@@ -1977,6 +2389,7 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1977
2389
  api,
1978
2390
  applyPolicy,
1979
2391
  controller,
2392
+ diagnosticStore,
1980
2393
  initialMessages,
1981
2394
  modelId,
1982
2395
  onApplyReceipts,
@@ -1989,16 +2402,22 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1989
2402
  });
1990
2403
  const hasSubmittedMessageRef = useRef((initialMessages?.length ?? 0) > 0);
1991
2404
  const showAssistantError = Boolean(authorizationError) || Boolean(error);
1992
- const assistantErrorPresentation = getAssistantErrorPresentation(authorizationError ?? error, t);
2405
+ const assistantErrorPresentation = getAssistantErrorPresentation({
2406
+ authorizationError,
2407
+ error,
2408
+ t
2409
+ });
1993
2410
  const lastMessage = messages.at(-1);
1994
2411
  const isAssistantTurnActive = isSurveyAssistantTurnActive({
1995
2412
  isStreaming,
1996
2413
  state
1997
2414
  });
1998
2415
  const promptTextareaRef = useRef(null);
2416
+ const exportDetailsButtonRef = useRef(null);
1999
2417
  const shouldRestorePromptFocusRef = useRef(false);
2000
2418
  const turnWasActiveRef = useRef(false);
2001
2419
  const [loadingEarlierMessages, setLoadingEarlierMessages] = useState(false);
2420
+ const [providerMessageExpanded, setProviderMessageExpanded] = useState(false);
2002
2421
  const loadEarlierMessages = useCallback(async () => {
2003
2422
  if (loadingEarlierMessages) return;
2004
2423
  setLoadingEarlierMessages(true);
@@ -2219,15 +2638,40 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
2219
2638
  className: "mt-0.5 wrap-break-word text-muted-foreground",
2220
2639
  children: assistantErrorPresentation.description
2221
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
+ }),
2222
2657
  /* @__PURE__ */ jsx("p", {
2223
2658
  className: "mt-1 text-muted-foreground/80",
2224
2659
  children: assistantErrorPresentation.hint
2225
2660
  }),
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")
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
+ })]
2231
2675
  })
2232
2676
  ]
2233
2677
  })]
@@ -2282,8 +2726,9 @@ const listThreadPage = async (client, cursor) => {
2282
2726
  if (client.listPage) return client.listPage(cursor ? { cursor } : void 0);
2283
2727
  return cursor ? { threads: [] } : { threads: await client.list() };
2284
2728
  };
2285
- 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 }) => {
2286
2730
  const t = useSurveyAssistantTranslation();
2731
+ const supportBundleEnabled = isSurveyAssistantSupportBundleEnabled(supportBundle);
2287
2732
  const threadClient = useMemo(() => providedThreadClient ?? createSurveyAssistantThreadClient(threadApi), [providedThreadClient, threadApi]);
2288
2733
  const storageKey = selectedThreadStorageKey === false ? null : selectedThreadStorageKey ?? `${SELECTED_THREAD_STORAGE_KEY}:${encodeURIComponent(resourceId)}${surveyId ? `:${encodeURIComponent(surveyId)}` : ""}`;
2289
2734
  const [threads, setThreads] = useState([]);
@@ -2302,8 +2747,23 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2302
2747
  const [assistantTurnActive, setAssistantTurnActive] = useState(false);
2303
2748
  const [initialLaunch, setInitialLaunch] = useState();
2304
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();
2305
2761
  const historyControlsRef = useRef(null);
2762
+ const historyButtonRef = useRef(null);
2306
2763
  const loadingAllThreadsRef = useRef(false);
2764
+ const supportBundleAbortRef = useRef(null);
2765
+ const supportBundleFocusReturnRef = useRef(null);
2766
+ const supportBundleThreadIdRef = useRef(void 0);
2307
2767
  const selectModel = useCallback((modelId) => {
2308
2768
  if (!models.some((model) => model.id === modelId)) return;
2309
2769
  onModelChange(modelId);
@@ -2529,10 +2989,67 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2529
2989
  renameThread,
2530
2990
  threads
2531
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
+ }, []);
2532
3048
  const deleteThread = useCallback(async (thread) => {
2533
3049
  if (!(onConfirmDeleteThread ? await onConfirmDeleteThread(thread) : window.confirm(t("threads.deleteConfirm", { title: thread.title })))) return;
2534
3050
  try {
2535
3051
  await threadClient.delete(thread.id);
3052
+ diagnosticStore.deleteThread(thread.id);
2536
3053
  const remaining = threads.filter((item) => item.id !== thread.id);
2537
3054
  setThreads(remaining);
2538
3055
  if (selectedThread?.id === thread.id) if (remaining[0]) await selectThread(remaining[0]);
@@ -2542,6 +3059,7 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2542
3059
  }
2543
3060
  }, [
2544
3061
  createThread,
3062
+ diagnosticStore,
2545
3063
  onConfirmDeleteThread,
2546
3064
  selectThread,
2547
3065
  selectedThread,
@@ -2628,6 +3146,14 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2628
3146
  promptFocusThreadId,
2629
3147
  selectedThread?.id
2630
3148
  ]);
3149
+ useEffect(() => {
3150
+ if (!supportBundleOpen || supportBundleThreadIdRef.current === selectedThread?.id) return;
3151
+ closeSupportBundle();
3152
+ }, [
3153
+ closeSupportBundle,
3154
+ selectedThread?.id,
3155
+ supportBundleOpen
3156
+ ]);
2631
3157
  useEffect(() => {
2632
3158
  const handleFocus = () => {
2633
3159
  refreshThreadList(false);
@@ -2699,6 +3225,7 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2699
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",
2700
3226
  disabled: assistantTurnActive,
2701
3227
  onClick: toggleHistory,
3228
+ ref: historyButtonRef,
2702
3229
  title: t("threads.history"),
2703
3230
  type: "button",
2704
3231
  children: /* @__PURE__ */ jsx(HistoryIcon, { className: "size-4" })
@@ -2789,6 +3316,15 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2789
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")
2790
3317
  })
2791
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
+ })
2792
3328
  })
2793
3329
  ]
2794
3330
  })
@@ -2796,46 +3332,63 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2796
3332
  });
2797
3333
  return /* @__PURE__ */ jsxs("div", {
2798
3334
  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
- })]
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
+ ]
2839
3392
  });
2840
3393
  };
2841
3394
  const SurveyAssistantPanel = ({ language, localizations, ...props }) => /* @__PURE__ */ jsx("div", {