@case-framework/survey-assistant 0.1.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 (98) hide show
  1. package/README.md +212 -0
  2. package/dist/authoring-references-Cy8J-DWt.d.mts +48 -0
  3. package/dist/authoring-references-Cy8J-DWt.d.mts.map +1 -0
  4. package/dist/authoring-references-X6uRaWuf.mjs +68 -0
  5. package/dist/authoring-references-X6uRaWuf.mjs.map +1 -0
  6. package/dist/capabilities-B6tvE7fK.mjs +66 -0
  7. package/dist/capabilities-B6tvE7fK.mjs.map +1 -0
  8. package/dist/capabilities-DvSKtjAw.d.mts +182 -0
  9. package/dist/capabilities-DvSKtjAw.d.mts.map +1 -0
  10. package/dist/capabilities-default.d.mts +15 -0
  11. package/dist/capabilities-default.d.mts.map +1 -0
  12. package/dist/capabilities-default.mjs +347 -0
  13. package/dist/capabilities-default.mjs.map +1 -0
  14. package/dist/constants-CFUWrRtl.mjs +7 -0
  15. package/dist/constants-CFUWrRtl.mjs.map +1 -0
  16. package/dist/constants-Jw0k8ojS.d.mts +67 -0
  17. package/dist/constants-Jw0k8ojS.d.mts.map +1 -0
  18. package/dist/controller-proxy-BRDusFiO.d.mts +57 -0
  19. package/dist/controller-proxy-BRDusFiO.d.mts.map +1 -0
  20. package/dist/controller-proxy-Duxo2IFE.mjs +92 -0
  21. package/dist/controller-proxy-Duxo2IFE.mjs.map +1 -0
  22. package/dist/digest-BsoVq49o.d.mts +9 -0
  23. package/dist/digest-BsoVq49o.d.mts.map +1 -0
  24. package/dist/digest.d.mts +2 -0
  25. package/dist/digest.mjs +20 -0
  26. package/dist/digest.mjs.map +1 -0
  27. package/dist/engine-sPvuixWg.mjs +4857 -0
  28. package/dist/engine-sPvuixWg.mjs.map +1 -0
  29. package/dist/engine.d.mts +3 -0
  30. package/dist/engine.mjs +4 -0
  31. package/dist/index-BWNHkpkh.d.mts +187 -0
  32. package/dist/index-BWNHkpkh.d.mts.map +1 -0
  33. package/dist/index-CUqbBjkc.d.mts +641 -0
  34. package/dist/index-CUqbBjkc.d.mts.map +1 -0
  35. package/dist/index-MpEMQzTm.d.mts +123 -0
  36. package/dist/index-MpEMQzTm.d.mts.map +1 -0
  37. package/dist/index-TFtvBiSQ.d.mts +1149 -0
  38. package/dist/index-TFtvBiSQ.d.mts.map +1 -0
  39. package/dist/memory-thread-repository-CJkkVE2-.mjs +718 -0
  40. package/dist/memory-thread-repository-CJkkVE2-.mjs.map +1 -0
  41. package/dist/protocol-DV0ka9WT.mjs +1032 -0
  42. package/dist/protocol-DV0ka9WT.mjs.map +1 -0
  43. package/dist/protocol.d.mts +3 -0
  44. package/dist/protocol.mjs +2 -0
  45. package/dist/react-VuYe-cPh.mjs +521 -0
  46. package/dist/react-VuYe-cPh.mjs.map +1 -0
  47. package/dist/react-integration.d.mts +2 -0
  48. package/dist/react-integration.mjs +3 -0
  49. package/dist/react.d.mts +3 -0
  50. package/dist/react.mjs +4 -0
  51. package/dist/references/assistant-operations.md +287 -0
  52. package/dist/references/core-rules.md +11 -0
  53. package/dist/references/embedded-forms.md +80 -0
  54. package/dist/references/expressions.md +301 -0
  55. package/dist/references/item-types.md +364 -0
  56. package/dist/references/localization.md +25 -0
  57. package/dist/references/rich-text-content.md +193 -0
  58. package/dist/references/source-material-surveys.md +60 -0
  59. package/dist/references/survey-data-model.md +109 -0
  60. package/dist/references/survey-quality.md +23 -0
  61. package/dist/request-context-jcmXT_Q2.mjs +44 -0
  62. package/dist/request-context-jcmXT_Q2.mjs.map +1 -0
  63. package/dist/request-guardrails-C59IWnH8.mjs +73 -0
  64. package/dist/request-guardrails-C59IWnH8.mjs.map +1 -0
  65. package/dist/server-agent.d.mts +131 -0
  66. package/dist/server-agent.d.mts.map +1 -0
  67. package/dist/server-agent.mjs +229 -0
  68. package/dist/server-agent.mjs.map +1 -0
  69. package/dist/server-runtime.d.mts +123 -0
  70. package/dist/server-runtime.d.mts.map +1 -0
  71. package/dist/server-runtime.mjs +296 -0
  72. package/dist/server-runtime.mjs.map +1 -0
  73. package/dist/server-tasks.d.mts +78 -0
  74. package/dist/server-tasks.d.mts.map +1 -0
  75. package/dist/server-tasks.mjs +253 -0
  76. package/dist/server-tasks.mjs.map +1 -0
  77. package/dist/server-tools.d.mts +2 -0
  78. package/dist/server-tools.mjs +2 -0
  79. package/dist/server.d.mts +52 -0
  80. package/dist/server.d.mts.map +1 -0
  81. package/dist/server.mjs +6 -0
  82. package/dist/storage-postgres.d.mts +37 -0
  83. package/dist/storage-postgres.d.mts.map +1 -0
  84. package/dist/storage-postgres.mjs +167 -0
  85. package/dist/storage-postgres.mjs.map +1 -0
  86. package/dist/thread-documents-DamIq9Od.d.mts +91 -0
  87. package/dist/thread-documents-DamIq9Od.d.mts.map +1 -0
  88. package/dist/thread-handlers-DpynUc64.d.mts +112 -0
  89. package/dist/thread-handlers-DpynUc64.d.mts.map +1 -0
  90. package/dist/tools-Hz2p7vyv.mjs +1879 -0
  91. package/dist/tools-Hz2p7vyv.mjs.map +1 -0
  92. package/dist/ui.css +2 -0
  93. package/dist/ui.d.mts +63 -0
  94. package/dist/ui.d.mts.map +1 -0
  95. package/dist/ui.mjs +2238 -0
  96. package/dist/ui.mjs.map +1 -0
  97. package/package.json +166 -0
  98. package/ui.css.d.ts +3 -0
package/dist/ui.mjs ADDED
@@ -0,0 +1,2238 @@
1
+ "use client";
2
+ import { Dt as parseSurveyAssistantError } from "./protocol-DV0ka9WT.mjs";
3
+ import { f as unwrapSurveyAssistantToolOutput, i as createSurveyAssistantThreadClient, l as parseSurveyChangeSet, p as isSurveyAssistantTurnActive, r as useSurveyAssistantModelCatalog, t as useSurveyAssistantSession } from "./react-VuYe-cPh.mjs";
4
+ import { getToolName, isFileUIPart, isReasoningUIPart, isToolUIPart } from "ai";
5
+ import { createContext, createElement, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
6
+ import { Collapsible, Select, Slot } from "radix-ui";
7
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
+ import { clsx } from "clsx";
9
+ import { twMerge } from "tailwind-merge";
10
+ import { useControllableState } from "@radix-ui/react-use-controllable-state";
11
+ import { ArrowDownIcon, ArrowUpIcon, BotIcon, BrainIcon, CheckCircleIcon, CheckIcon, ChevronDownIcon, ChevronRightIcon, ChevronUpIcon, ClockIcon, DotIcon, FileIcon, HistoryIcon, ListChecksIcon, LoaderCircleIcon, MessageSquarePlusIcon, PaperclipIcon, PencilIcon, PlusIcon, SparklesIcon, SquareIcon, Trash2Icon, WandSparklesIcon, WrenchIcon, XCircleIcon, XIcon } from "lucide-react";
12
+ import { motion } from "motion/react";
13
+ import { createLocalizationTranslator, mergeLocalizationCatalogs } from "@case-framework/ui-localization";
14
+ import { cva } from "class-variance-authority";
15
+ import { StickToBottom, useStickToBottomContext } from "use-stick-to-bottom";
16
+ import { Streamdown } from "streamdown";
17
+ import { createHighlighter } from "shiki";
18
+ //#region src/ui/internal/collapsible.tsx
19
+ function Collapsible$1({ ...props }) {
20
+ return /* @__PURE__ */ jsx(Collapsible.Root, {
21
+ "data-slot": "collapsible",
22
+ ...props
23
+ });
24
+ }
25
+ function CollapsibleTrigger({ ...props }) {
26
+ return /* @__PURE__ */ jsx(Collapsible.CollapsibleTrigger, {
27
+ "data-slot": "collapsible-trigger",
28
+ ...props
29
+ });
30
+ }
31
+ function CollapsibleContent({ ...props }) {
32
+ return /* @__PURE__ */ jsx(Collapsible.CollapsibleContent, {
33
+ "data-slot": "collapsible-content",
34
+ ...props
35
+ });
36
+ }
37
+ //#endregion
38
+ //#region src/ui/utils.ts
39
+ const cn = (...inputs) => twMerge(clsx(inputs));
40
+ //#endregion
41
+ //#region src/ui/internal/shimmer.tsx
42
+ const motionComponentCache = /* @__PURE__ */ new Map();
43
+ const getMotionComponent = (element) => {
44
+ let component = motionComponentCache.get(element);
45
+ if (!component) {
46
+ component = motion.create(element);
47
+ motionComponentCache.set(element, component);
48
+ }
49
+ return component;
50
+ };
51
+ const ShimmerComponent = ({ children, as: Component = "p", className, duration = 2, spread = 2 }) => {
52
+ const MotionComponent = getMotionComponent(Component);
53
+ const dynamicSpread = useMemo(() => (children?.length ?? 0) * spread, [children, spread]);
54
+ return /* @__PURE__ */ jsx(MotionComponent, {
55
+ animate: { backgroundPosition: "0% center" },
56
+ className: cn("relative inline-block bg-[length:250%_100%,auto] bg-clip-text text-transparent", "[--bg:linear-gradient(90deg,#0000_calc(50%-var(--spread)),var(--color-background),#0000_calc(50%+var(--spread)))] [background-repeat:no-repeat,padding-box]", className),
57
+ initial: { backgroundPosition: "100% center" },
58
+ style: {
59
+ "--spread": `${dynamicSpread}px`,
60
+ backgroundImage: "var(--bg), linear-gradient(var(--color-muted-foreground), var(--color-muted-foreground))"
61
+ },
62
+ transition: {
63
+ duration,
64
+ ease: "linear",
65
+ repeat: Number.POSITIVE_INFINITY
66
+ },
67
+ children
68
+ });
69
+ };
70
+ const Shimmer = memo(ShimmerComponent);
71
+ //#endregion
72
+ //#region src/ui/localizations/en.ts
73
+ const surveyAssistantEnMessages = {
74
+ panel: {
75
+ ariaLabel: "Survey assistant",
76
+ title: "Assistant",
77
+ loadingChats: "Loading chats",
78
+ controlsUnavailable: "Assistant controls unavailable"
79
+ },
80
+ empty: {
81
+ title: "What are you working on?",
82
+ description: "Draft, inspect, or refine your survey - just describe what you need, or start with one of these."
83
+ },
84
+ quickActions: {
85
+ review: {
86
+ label: "Review survey",
87
+ description: "Spot issues and suggest improvements",
88
+ prompt: "Review the current survey and suggest improvements."
89
+ },
90
+ create: {
91
+ label: "New question",
92
+ description: "Draft a question to add",
93
+ prompt: "Help me create a new survey question."
94
+ },
95
+ refine: {
96
+ label: "Refine wording",
97
+ description: "Polish respondent-facing text",
98
+ prompt: "Suggest improvements to the respondent-facing wording."
99
+ }
100
+ },
101
+ input: {
102
+ placeholder: "Ask about the survey...",
103
+ attachFiles: "Attach files",
104
+ dropFiles: "Drop images or PDFs to attach",
105
+ unsupportedFiles: "Unsupported file type: {filenames}",
106
+ removeFile: "Remove {filename}",
107
+ send: "Send message",
108
+ stop: "Stop response",
109
+ model: "Assistant model",
110
+ defaultModel: "Default model"
111
+ },
112
+ conversation: {
113
+ noMessages: "No messages yet",
114
+ startConversation: "Start a conversation to see messages here",
115
+ scrollToLatest: "Scroll to latest message",
116
+ working: "Working...",
117
+ stopped: "Stopped"
118
+ },
119
+ status: {
120
+ applying: "Applying changes",
121
+ applied: "Changes applied",
122
+ stopped: "Stopped",
123
+ attention: "Needs attention",
124
+ ready: "Ready",
125
+ connecting: "Connecting to the assistant",
126
+ thinking: "Thinking through your request",
127
+ writing: "Writing the response",
128
+ working: "Working on your request"
129
+ },
130
+ activity: {
131
+ label: "Activity",
132
+ working: "Working...",
133
+ workedFor: "Worked for {duration}",
134
+ technicalDetails: "View technical details",
135
+ hideTechnicalDetails: "Hide technical details",
136
+ waitingApproval: "Waiting for approval",
137
+ approvalReceived: "Approval received",
138
+ continuingApproval: "Continuing after approval",
139
+ preparingStep: "Preparing the next step",
140
+ usingResult: "Using the result",
141
+ continuingWithoutStep: "Continuing without that step",
142
+ recoveringToolError: "Recovering from a tool error",
143
+ skipped: "Skipped",
144
+ failed: "Failed",
145
+ workingShort: "Working",
146
+ done: "Done",
147
+ stopped: "Stopped",
148
+ expressionChange: "Expression change",
149
+ surveyChange: "Survey change",
150
+ genericActive: "Working with survey",
151
+ genericComplete: "Survey step complete",
152
+ failedSuffix: "{name} failed",
153
+ skippedSuffix: "{name} skipped",
154
+ tools: {
155
+ survey_inspect: {
156
+ active: "Reviewing survey",
157
+ complete: "Survey reviewed"
158
+ },
159
+ survey_change: {
160
+ active: "Preparing change",
161
+ complete: "Change prepared"
162
+ },
163
+ survey_expression: {
164
+ active: "Preparing expression",
165
+ complete: "Expression prepared"
166
+ },
167
+ survey_authoring_reference: {
168
+ active: "Checking authoring guidance",
169
+ complete: "Authoring guidance checked"
170
+ },
171
+ survey_attachment: {
172
+ active: "Opening attachment",
173
+ complete: "Attachment opened"
174
+ },
175
+ recall: {
176
+ active: "Checking earlier conversation",
177
+ complete: "Earlier conversation checked"
178
+ }
179
+ }
180
+ },
181
+ changes: {
182
+ applied: "Change applied",
183
+ stale: "Change not applied",
184
+ failed: "Change could not be applied",
185
+ prepared: "Change prepared",
186
+ rejected: "Change could not be prepared",
187
+ operationCount: "{count} operation",
188
+ operationCountPlural: "{count} operations",
189
+ issueCount: "{count} issue",
190
+ issueCountPlural: "{count} issues",
191
+ warningCount: "{count} warning",
192
+ warningCountPlural: "{count} warnings",
193
+ incompleteTitle: "The requested changes were not completed.",
194
+ blocked: "No survey changes were applied because {count} rejected change proposal remains unresolved.",
195
+ blockedPlural: "No survey changes were applied because {count} rejected change proposals remain unresolved.",
196
+ partial: "{applied} change set was applied; {failed} could not be applied.",
197
+ partialPlural: "{applied} change sets were applied; {failed} could not be applied.",
198
+ reviewBeforeRetry: "Review the change steps above before retrying."
199
+ },
200
+ errors: {
201
+ dismiss: "Dismiss",
202
+ dismissAssistant: "Dismiss assistant error",
203
+ retry: "Try again",
204
+ fallbackDetail: "The assistant hit an error on the previous turn.",
205
+ model_unavailable: {
206
+ title: "Model unavailable",
207
+ hint: "Select a model available to this account, then send the message again."
208
+ },
209
+ rate_limited: {
210
+ title: "Assistant temporarily busy",
211
+ hint: "Wait a moment, then send the message again."
212
+ },
213
+ authorization_failed: {
214
+ title: "Assistant unavailable",
215
+ hint: "Check your access or sign in again before retrying."
216
+ },
217
+ attachment_unavailable: {
218
+ title: "Attachment unavailable",
219
+ hint: "Attach the file again or continue without it."
220
+ },
221
+ request_failed: {
222
+ title: "Assistant could not complete request",
223
+ hint: "You can continue with another message."
224
+ }
225
+ },
226
+ threads: {
227
+ newChat: "New chat",
228
+ history: "Chat history",
229
+ recent: "Recent chats",
230
+ automaticTitle: "Use automatic title",
231
+ automaticShort: "Auto",
232
+ rename: "Rename {title}",
233
+ renameTitle: "Rename chat",
234
+ delete: "Delete {title}",
235
+ deleteTitle: "Delete chat",
236
+ deleteConfirm: "Delete \"{title}\" and its attachments?"
237
+ }
238
+ };
239
+ //#endregion
240
+ //#region src/ui/localizations/nl.ts
241
+ const surveyAssistantNlMessages = {
242
+ panel: {
243
+ ariaLabel: "Surveyassistent",
244
+ title: "Assistent",
245
+ loadingChats: "Chats laden",
246
+ controlsUnavailable: "Bediening van de assistent niet beschikbaar"
247
+ },
248
+ empty: {
249
+ title: "Waar werk je aan?",
250
+ description: "Maak, controleer of verbeter je survey. Beschrijf wat je nodig hebt of kies een van deze opties."
251
+ },
252
+ quickActions: {
253
+ review: {
254
+ label: "Survey controleren",
255
+ description: "Problemen vinden en verbeteringen voorstellen",
256
+ prompt: "Controleer de huidige survey en stel verbeteringen voor."
257
+ },
258
+ create: {
259
+ label: "Nieuwe vraag",
260
+ description: "Een vraag opstellen en toevoegen",
261
+ prompt: "Help me een nieuwe surveyvraag te maken."
262
+ },
263
+ refine: {
264
+ label: "Formulering verbeteren",
265
+ description: "Tekst voor respondenten aanscherpen",
266
+ prompt: "Stel verbeteringen voor voor de tekst die respondenten zien."
267
+ }
268
+ },
269
+ input: {
270
+ placeholder: "Vraag iets over de survey...",
271
+ attachFiles: "Bestanden toevoegen",
272
+ dropFiles: "Sleep afbeeldingen of pdf's hierheen om ze toe te voegen",
273
+ unsupportedFiles: "Niet-ondersteund bestandstype: {filenames}",
274
+ removeFile: "{filename} verwijderen",
275
+ send: "Bericht verzenden",
276
+ stop: "Antwoord stoppen",
277
+ model: "Assistentmodel",
278
+ defaultModel: "Standaardmodel"
279
+ },
280
+ conversation: {
281
+ noMessages: "Nog geen berichten",
282
+ startConversation: "Start een gesprek om hier berichten te zien",
283
+ scrollToLatest: "Naar het nieuwste bericht scrollen",
284
+ working: "Bezig...",
285
+ stopped: "Gestopt"
286
+ },
287
+ status: {
288
+ applying: "Wijzigingen toepassen",
289
+ applied: "Wijzigingen toegepast",
290
+ stopped: "Gestopt",
291
+ attention: "Aandacht nodig",
292
+ ready: "Gereed",
293
+ connecting: "Verbinding maken met de assistent",
294
+ thinking: "Verzoek doordenken",
295
+ writing: "Antwoord schrijven",
296
+ working: "Aan je verzoek werken"
297
+ },
298
+ activity: {
299
+ label: "Activiteit",
300
+ working: "Bezig...",
301
+ workedFor: "{duration} gewerkt",
302
+ technicalDetails: "Technische details tonen",
303
+ hideTechnicalDetails: "Technische details verbergen",
304
+ waitingApproval: "Wachten op goedkeuring",
305
+ approvalReceived: "Goedkeuring ontvangen",
306
+ continuingApproval: "Doorgaan na goedkeuring",
307
+ preparingStep: "Volgende stap voorbereiden",
308
+ usingResult: "Resultaat gebruiken",
309
+ continuingWithoutStep: "Doorgaan zonder die stap",
310
+ recoveringToolError: "Herstellen van een fout in een hulpmiddel",
311
+ skipped: "Overgeslagen",
312
+ failed: "Mislukt",
313
+ workingShort: "Bezig",
314
+ done: "Gereed",
315
+ stopped: "Gestopt",
316
+ expressionChange: "Expressiewijziging",
317
+ surveyChange: "Surveywijziging",
318
+ genericActive: "Werken met de survey",
319
+ genericComplete: "Surveystap voltooid",
320
+ failedSuffix: "{name} mislukt",
321
+ skippedSuffix: "{name} overgeslagen",
322
+ tools: {
323
+ survey_inspect: {
324
+ active: "Survey controleren",
325
+ complete: "Survey gecontroleerd"
326
+ },
327
+ survey_change: {
328
+ active: "Wijziging voorbereiden",
329
+ complete: "Wijziging voorbereid"
330
+ },
331
+ survey_expression: {
332
+ active: "Expressie voorbereiden",
333
+ complete: "Expressie voorbereid"
334
+ },
335
+ survey_authoring_reference: {
336
+ active: "Auteursrichtlijnen controleren",
337
+ complete: "Auteursrichtlijnen gecontroleerd"
338
+ },
339
+ survey_attachment: {
340
+ active: "Bijlage openen",
341
+ complete: "Bijlage geopend"
342
+ },
343
+ recall: {
344
+ active: "Eerder gesprek controleren",
345
+ complete: "Eerder gesprek gecontroleerd"
346
+ }
347
+ }
348
+ },
349
+ changes: {
350
+ applied: "Wijziging toegepast",
351
+ stale: "Wijziging niet toegepast",
352
+ failed: "Wijziging kon niet worden toegepast",
353
+ prepared: "Wijziging voorbereid",
354
+ rejected: "Wijziging kon niet worden voorbereid",
355
+ operationCount: "{count} bewerking",
356
+ operationCountPlural: "{count} bewerkingen",
357
+ issueCount: "{count} probleem",
358
+ issueCountPlural: "{count} problemen",
359
+ warningCount: "{count} waarschuwing",
360
+ warningCountPlural: "{count} waarschuwingen",
361
+ incompleteTitle: "De gevraagde wijzigingen zijn niet voltooid.",
362
+ blocked: "Er zijn geen surveywijzigingen toegepast omdat {count} afgewezen wijzigingsvoorstel nog niet is opgelost.",
363
+ blockedPlural: "Er zijn geen surveywijzigingen toegepast omdat {count} afgewezen wijzigingsvoorstellen nog niet zijn opgelost.",
364
+ partial: "{applied} wijzigingsset is toegepast; {failed} kon niet worden toegepast.",
365
+ partialPlural: "{applied} wijzigingssets zijn toegepast; {failed} konden niet worden toegepast.",
366
+ reviewBeforeRetry: "Controleer de bovenstaande wijzigingsstappen voordat je het opnieuw probeert."
367
+ },
368
+ errors: {
369
+ dismiss: "Sluiten",
370
+ dismissAssistant: "Assistentfout sluiten",
371
+ retry: "Opnieuw proberen",
372
+ fallbackDetail: "Er is tijdens de vorige beurt een fout opgetreden.",
373
+ model_unavailable: {
374
+ title: "Model niet beschikbaar",
375
+ hint: "Selecteer een model dat beschikbaar is voor dit account en verzend het bericht opnieuw."
376
+ },
377
+ rate_limited: {
378
+ title: "Assistent tijdelijk bezet",
379
+ hint: "Wacht even en verzend het bericht daarna opnieuw."
380
+ },
381
+ authorization_failed: {
382
+ title: "Assistent niet beschikbaar",
383
+ hint: "Controleer je toegang of meld je opnieuw aan voordat je het opnieuw probeert."
384
+ },
385
+ attachment_unavailable: {
386
+ title: "Bijlage niet beschikbaar",
387
+ hint: "Voeg het bestand opnieuw toe of ga verder zonder het bestand."
388
+ },
389
+ request_failed: {
390
+ title: "Assistent kon het verzoek niet voltooien",
391
+ hint: "Je kunt doorgaan met een nieuw bericht."
392
+ }
393
+ },
394
+ threads: {
395
+ newChat: "Nieuwe chat",
396
+ history: "Chatgeschiedenis",
397
+ recent: "Recente chats",
398
+ automaticTitle: "Automatische titel gebruiken",
399
+ automaticShort: "Automatisch",
400
+ rename: "{title} hernoemen",
401
+ renameTitle: "Chat hernoemen",
402
+ delete: "{title} verwijderen",
403
+ deleteTitle: "Chat verwijderen",
404
+ deleteConfirm: "\"{title}\" en de bijlagen verwijderen?"
405
+ }
406
+ };
407
+ //#endregion
408
+ //#region src/ui/localization.tsx
409
+ const defaultSurveyAssistantLocalizations = {
410
+ en: surveyAssistantEnMessages,
411
+ nl: surveyAssistantNlMessages
412
+ };
413
+ const SurveyAssistantLocalizationContext = createContext(null);
414
+ const SurveyAssistantLocalizationProvider = ({ children, language = "en", localizations }) => {
415
+ const translate = useMemo(() => createLocalizationTranslator({
416
+ fallbackLocale: "en",
417
+ locale: language,
418
+ localizations: mergeLocalizationCatalogs(defaultSurveyAssistantLocalizations, localizations),
419
+ onMissing: (key, locale) => console.log(`[SurveyAssistant] Missing localization for key "${key}" in language "${locale}".`)
420
+ }), [language, localizations]);
421
+ return /* @__PURE__ */ jsx(SurveyAssistantLocalizationContext.Provider, {
422
+ value: translate,
423
+ children
424
+ });
425
+ };
426
+ const useSurveyAssistantTranslation = () => {
427
+ const translate = useContext(SurveyAssistantLocalizationContext);
428
+ if (!translate) throw new Error("useSurveyAssistantTranslation must be used within SurveyAssistantLocalizationProvider.");
429
+ return translate;
430
+ };
431
+ //#endregion
432
+ //#region src/ui/internal/chain-of-thought.tsx
433
+ const ChainOfThoughtContext = createContext(null);
434
+ const useChainOfThought = () => {
435
+ const context = useContext(ChainOfThoughtContext);
436
+ if (!context) throw new Error("ChainOfThought components must be used within ChainOfThought");
437
+ return context;
438
+ };
439
+ const AUTO_CLOSE_DELAY = 1e3;
440
+ const MS_IN_S = 1e3;
441
+ const formatDuration = (durationSeconds) => {
442
+ const hours = Math.floor(durationSeconds / 3600);
443
+ const minutes = Math.floor(durationSeconds % 3600 / 60);
444
+ const seconds = durationSeconds % 60;
445
+ if (hours > 0) return `${hours}h ${minutes}m`;
446
+ if (minutes > 0) return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
447
+ return `${seconds}s`;
448
+ };
449
+ const ChainOfThought = memo(({ className, isStreaming = false, open, defaultOpen, defaultDuration, onOpenChange, duration: durationProp, autoClose = true, children, ...props }) => {
450
+ const resolvedDefaultOpen = defaultOpen ?? isStreaming;
451
+ const isExplicitlyClosed = defaultOpen === false;
452
+ const [isOpen, setIsOpen] = useControllableState({
453
+ defaultProp: resolvedDefaultOpen,
454
+ onChange: onOpenChange,
455
+ prop: open
456
+ });
457
+ const [duration, setDuration] = useControllableState({
458
+ defaultProp: defaultDuration,
459
+ prop: durationProp
460
+ });
461
+ const hasEverStreamedRef = useRef(isStreaming);
462
+ const [hasAutoClosed, setHasAutoClosed] = useState(false);
463
+ const startTimeRef = useRef(null);
464
+ useEffect(() => {
465
+ if (isStreaming) {
466
+ hasEverStreamedRef.current = true;
467
+ if (startTimeRef.current === null) startTimeRef.current = Date.now();
468
+ } else if (startTimeRef.current !== null) {
469
+ setDuration(Math.ceil((Date.now() - startTimeRef.current) / MS_IN_S));
470
+ startTimeRef.current = null;
471
+ }
472
+ }, [isStreaming, setDuration]);
473
+ useEffect(() => {
474
+ if (isStreaming && !isOpen && !isExplicitlyClosed) setIsOpen(true);
475
+ }, [
476
+ isStreaming,
477
+ isOpen,
478
+ setIsOpen,
479
+ isExplicitlyClosed
480
+ ]);
481
+ useEffect(() => {
482
+ if (autoClose && hasEverStreamedRef.current && !isStreaming && isOpen && !hasAutoClosed) {
483
+ const timer = setTimeout(() => {
484
+ setIsOpen(false);
485
+ setHasAutoClosed(true);
486
+ }, AUTO_CLOSE_DELAY);
487
+ return () => clearTimeout(timer);
488
+ }
489
+ }, [
490
+ autoClose,
491
+ isStreaming,
492
+ isOpen,
493
+ setIsOpen,
494
+ hasAutoClosed
495
+ ]);
496
+ const handleOpenChange = useCallback((newOpen) => {
497
+ setIsOpen(newOpen);
498
+ }, [setIsOpen]);
499
+ const contextValue = useMemo(() => ({
500
+ duration,
501
+ isOpen,
502
+ isStreaming,
503
+ setIsOpen
504
+ }), [
505
+ duration,
506
+ isOpen,
507
+ isStreaming,
508
+ setIsOpen
509
+ ]);
510
+ return /* @__PURE__ */ jsx(ChainOfThoughtContext.Provider, {
511
+ value: contextValue,
512
+ children: /* @__PURE__ */ jsx(Collapsible$1, {
513
+ className: cn("not-prose w-full", className),
514
+ onOpenChange: handleOpenChange,
515
+ open: isOpen,
516
+ ...props,
517
+ children
518
+ })
519
+ });
520
+ });
521
+ const ChainOfThoughtHeader = memo(({ className, children, getThinkingMessage, ...props }) => {
522
+ const { isOpen, isStreaming, duration } = useChainOfThought();
523
+ const t = useSurveyAssistantTranslation();
524
+ const defaultThinkingMessage = isStreaming ? /* @__PURE__ */ jsx(Shimmer, {
525
+ duration: 1,
526
+ children: t("activity.working")
527
+ }) : duration === void 0 ? /* @__PURE__ */ jsx("span", { children: t("activity.label") }) : /* @__PURE__ */ jsx("span", { children: t("activity.workedFor", { duration: formatDuration(duration) }) });
528
+ return /* @__PURE__ */ jsxs(CollapsibleTrigger, {
529
+ className: cn("flex w-full items-center gap-1.5 text-muted-foreground text-sm transition-colors hover:text-foreground", className),
530
+ ...props,
531
+ children: [
532
+ /* @__PURE__ */ jsx(BrainIcon, { className: "size-3.5 shrink-0" }),
533
+ /* @__PURE__ */ jsx("span", {
534
+ className: "flex-1 text-left",
535
+ children: children ?? getThinkingMessage?.(isStreaming, duration) ?? defaultThinkingMessage
536
+ }),
537
+ /* @__PURE__ */ jsx(ChevronDownIcon, { className: cn("size-3.5 shrink-0 transition-transform", isOpen ? "rotate-180" : "rotate-0") })
538
+ ]
539
+ });
540
+ });
541
+ const ChainOfThoughtContent = memo(({ className, ...props }) => /* @__PURE__ */ jsx(CollapsibleContent, {
542
+ className: cn("mt-2", "data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 outline-none data-[state=closed]:animate-out data-[state=open]:animate-in", className),
543
+ ...props
544
+ }));
545
+ const stepStatusStyles = {
546
+ complete: "text-muted-foreground",
547
+ active: "text-foreground",
548
+ pending: "text-muted-foreground/50"
549
+ };
550
+ const ChainOfThoughtStep = memo(({ className, icon: Icon = DotIcon, label, description, result, status = "complete", isLast = false, children, ...props }) => /* @__PURE__ */ jsxs("div", {
551
+ className: cn("fade-in-0 slide-in-from-top-2 relative animate-in text-xs", isLast ? "pb-0" : "pb-2.5", stepStatusStyles[status], className),
552
+ ...props,
553
+ children: [
554
+ /* @__PURE__ */ jsxs("div", {
555
+ className: "flex gap-2",
556
+ children: [/* @__PURE__ */ jsx("div", {
557
+ className: "relative z-10 mt-0.5 shrink-0 bg-background",
558
+ children: /* @__PURE__ */ jsx(Icon, { className: "size-3.5" })
559
+ }), /* @__PURE__ */ jsxs("div", {
560
+ className: "min-w-0 flex-1",
561
+ children: [/* @__PURE__ */ jsx("div", {
562
+ className: "wrap-break-word",
563
+ children: label
564
+ }), description && /* @__PURE__ */ jsx("div", {
565
+ className: "mt-1.5 text-muted-foreground text-xs",
566
+ children: description
567
+ })]
568
+ })]
569
+ }),
570
+ result && /* @__PURE__ */ jsx("div", {
571
+ className: "relative mt-1.5 ml-[22px]",
572
+ children: result
573
+ }),
574
+ children && /* @__PURE__ */ jsx("div", {
575
+ className: "mt-1.5 ml-[22px] space-y-1.5",
576
+ children
577
+ })
578
+ ]
579
+ }));
580
+ ChainOfThought.displayName = "ChainOfThought";
581
+ ChainOfThoughtHeader.displayName = "ChainOfThoughtHeader";
582
+ ChainOfThoughtContent.displayName = "ChainOfThoughtContent";
583
+ ChainOfThoughtStep.displayName = "ChainOfThoughtStep";
584
+ //#endregion
585
+ //#region src/ui/internal/button.tsx
586
+ const buttonVariants = cva("inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", {
587
+ variants: {
588
+ variant: {
589
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
590
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
591
+ ghost: "hover:bg-muted hover:text-foreground"
592
+ },
593
+ size: {
594
+ default: "h-9 px-4 py-2",
595
+ icon: "size-9",
596
+ "icon-sm": "size-8"
597
+ }
598
+ },
599
+ defaultVariants: {
600
+ variant: "default",
601
+ size: "default"
602
+ }
603
+ });
604
+ const Button = ({ className, variant = "default", size = "default", asChild = false, ...props }) => {
605
+ return /* @__PURE__ */ jsx(asChild ? Slot.Root : "button", {
606
+ className: cn(buttonVariants({
607
+ variant,
608
+ size,
609
+ className
610
+ })),
611
+ ...props
612
+ });
613
+ };
614
+ //#endregion
615
+ //#region src/ui/internal/conversation.tsx
616
+ const Conversation = ({ className, ...props }) => /* @__PURE__ */ jsx(StickToBottom, {
617
+ className: cn("relative min-h-0 flex-1 overflow-y-hidden", className),
618
+ initial: "smooth",
619
+ role: "log",
620
+ resize: "smooth",
621
+ ...props
622
+ });
623
+ const ConversationContent = ({ className, ...props }) => /* @__PURE__ */ jsx(StickToBottom.Content, {
624
+ className: cn("flex flex-col gap-8 p-4", className),
625
+ ...props
626
+ });
627
+ const ConversationEmptyState = ({ className, title, description, icon, children, ...props }) => {
628
+ const t = useSurveyAssistantTranslation();
629
+ const resolvedTitle = title ?? t("conversation.noMessages");
630
+ const resolvedDescription = description ?? t("conversation.startConversation");
631
+ return /* @__PURE__ */ jsx("div", {
632
+ className: cn("flex size-full flex-col items-center justify-center gap-3 p-8 text-center", className),
633
+ ...props,
634
+ children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [icon && /* @__PURE__ */ jsx("div", {
635
+ className: "text-muted-foreground",
636
+ children: icon
637
+ }), /* @__PURE__ */ jsxs("div", {
638
+ className: "space-y-1",
639
+ children: [/* @__PURE__ */ jsx("h3", {
640
+ className: "font-medium text-sm",
641
+ children: resolvedTitle
642
+ }), resolvedDescription && /* @__PURE__ */ jsx("p", {
643
+ className: "text-muted-foreground text-sm",
644
+ children: resolvedDescription
645
+ })]
646
+ })] })
647
+ });
648
+ };
649
+ const ConversationScrollButton = ({ className, ...props }) => {
650
+ const { isAtBottom, scrollToBottom } = useStickToBottomContext();
651
+ const t = useSurveyAssistantTranslation();
652
+ const handleScrollToBottom = useCallback(() => {
653
+ scrollToBottom();
654
+ }, [scrollToBottom]);
655
+ return !isAtBottom && /* @__PURE__ */ jsx(Button, {
656
+ className: cn("absolute left-1/2 z-10 size-8 -translate-x-1/2 rounded-full border border-background/60 bg-foreground text-background shadow-lg ring-1 ring-border/50 hover:bg-foreground/85 hover:text-background", className),
657
+ "aria-label": t("conversation.scrollToLatest"),
658
+ onClick: handleScrollToBottom,
659
+ size: "icon-sm",
660
+ type: "button",
661
+ variant: "secondary",
662
+ ...props,
663
+ children: /* @__PURE__ */ jsx(ArrowDownIcon, { className: "size-4" })
664
+ });
665
+ };
666
+ //#endregion
667
+ //#region src/ui/internal/message.tsx
668
+ const Message = ({ className, from, ...props }) => /* @__PURE__ */ jsx("div", {
669
+ className: cn("group flex w-full max-w-[95%] flex-col gap-2", from === "user" ? "ml-auto items-end" : "items-start", className),
670
+ "data-role": from,
671
+ ...props
672
+ });
673
+ const MessageContent = ({ children, className, ...props }) => /* @__PURE__ */ jsx("div", {
674
+ className: cn("w-fit min-w-0 max-w-full break-words text-sm", "group-data-[role=user]:whitespace-pre-wrap group-data-[role=user]:rounded-xl group-data-[role=user]:border group-data-[role=user]:border-blue-200/70 group-data-[role=user]:bg-blue-50 group-data-[role=user]:px-3 group-data-[role=user]:py-2 group-data-[role=user]:text-foreground group-data-[role=user]:shadow-xs dark:group-data-[role=user]:border-blue-800/60 dark:group-data-[role=user]:bg-blue-950/50", "group-data-[role=assistant]:text-foreground", className),
675
+ ...props,
676
+ children
677
+ });
678
+ //#endregion
679
+ //#region src/ui/internal/prompt-input.tsx
680
+ const SUPPORTED_FILE_ACCEPT = "image/*,application/pdf";
681
+ const isSupportedFile = (file) => file.type.startsWith("image/") || file.type === "application/pdf";
682
+ const formatFileSize = (size) => {
683
+ if (size < 1024 * 1024) return `${Math.max(1, Math.round(size / 1024))} KB`;
684
+ return `${(size / (1024 * 1024)).toFixed(size < 10 * 1024 * 1024 ? 1 : 0)} MB`;
685
+ };
686
+ const fileToUIPart = async (file) => {
687
+ const mediaType = file.type || "application/octet-stream";
688
+ const bytes = new Uint8Array(await file.arrayBuffer());
689
+ const chunkSize = 32768;
690
+ let binary = "";
691
+ for (let offset = 0; offset < bytes.length; offset += chunkSize) binary += String.fromCharCode(...bytes.subarray(offset, offset + chunkSize));
692
+ return {
693
+ type: "file",
694
+ mediaType,
695
+ filename: file.name,
696
+ url: `data:${mediaType};base64,${btoa(binary)}`
697
+ };
698
+ };
699
+ const PromptInputContext = createContext(null);
700
+ const usePromptInput = () => {
701
+ const context = useContext(PromptInputContext);
702
+ if (!context) throw new Error("PromptInput components must be used within PromptInput.");
703
+ return context;
704
+ };
705
+ const PromptInput = ({ attachmentsDisabled = false, children, className, onDragEnter, onDragLeave, onDragOver, onDrop, onPaste, onSubmit, ...props }) => {
706
+ const [value, setValue] = useState("");
707
+ const [files, setFiles] = useState([]);
708
+ const [isDraggingFiles, setIsDraggingFiles] = useState(false);
709
+ const [unsupportedFileNames, setUnsupportedFileNames] = useState([]);
710
+ const fileDragDepthRef = useRef(0);
711
+ const t = useSurveyAssistantTranslation();
712
+ const addFiles = useCallback((fileList) => {
713
+ const nextFiles = Array.from(fileList ?? []);
714
+ const supportedFiles = nextFiles.filter(isSupportedFile);
715
+ const unsupportedFiles = nextFiles.filter((file) => !isSupportedFile(file));
716
+ setFiles((currentFiles) => [...currentFiles, ...supportedFiles]);
717
+ setUnsupportedFileNames(unsupportedFiles.map((file) => file.name));
718
+ }, []);
719
+ const removeFile = useCallback((index) => {
720
+ setFiles((currentFiles) => currentFiles.filter((_, fileIndex) => fileIndex !== index));
721
+ }, []);
722
+ const contextValue = useMemo(() => ({
723
+ value,
724
+ setValue,
725
+ files,
726
+ addFiles,
727
+ removeFile,
728
+ unsupportedFileNames
729
+ }), [
730
+ addFiles,
731
+ files,
732
+ removeFile,
733
+ unsupportedFileNames,
734
+ value
735
+ ]);
736
+ const handleSubmit = useCallback(async (event) => {
737
+ event.preventDefault();
738
+ const text = value.trim();
739
+ if (!text && files.length === 0) return;
740
+ if (await onSubmit({
741
+ text,
742
+ files: await Promise.all(files.map(fileToUIPart))
743
+ }, event) === false) return;
744
+ setValue("");
745
+ setFiles([]);
746
+ setUnsupportedFileNames([]);
747
+ }, [
748
+ files,
749
+ onSubmit,
750
+ value
751
+ ]);
752
+ const isFileDrag = (event) => event.dataTransfer.types.includes("Files");
753
+ const handleDragEnter = (event) => {
754
+ onDragEnter?.(event);
755
+ if (event.defaultPrevented || !isFileDrag(event)) return;
756
+ event.preventDefault();
757
+ fileDragDepthRef.current += 1;
758
+ if (!attachmentsDisabled) setIsDraggingFiles(true);
759
+ };
760
+ const handleDragLeave = (event) => {
761
+ onDragLeave?.(event);
762
+ if (!isFileDrag(event)) return;
763
+ fileDragDepthRef.current = Math.max(0, fileDragDepthRef.current - 1);
764
+ if (fileDragDepthRef.current === 0) setIsDraggingFiles(false);
765
+ };
766
+ const handleDragOver = (event) => {
767
+ onDragOver?.(event);
768
+ if (event.defaultPrevented || !isFileDrag(event)) return;
769
+ event.preventDefault();
770
+ event.dataTransfer.dropEffect = attachmentsDisabled ? "none" : "copy";
771
+ };
772
+ const handleDrop = (event) => {
773
+ onDrop?.(event);
774
+ if (!isFileDrag(event)) return;
775
+ fileDragDepthRef.current = 0;
776
+ setIsDraggingFiles(false);
777
+ if (event.defaultPrevented) return;
778
+ event.preventDefault();
779
+ if (!attachmentsDisabled) addFiles(event.dataTransfer.files);
780
+ };
781
+ const handlePaste = (event) => {
782
+ onPaste?.(event);
783
+ if (event.defaultPrevented || attachmentsDisabled || event.clipboardData.files.length === 0) return;
784
+ event.preventDefault();
785
+ addFiles(event.clipboardData.files);
786
+ };
787
+ return /* @__PURE__ */ jsx(PromptInputContext.Provider, {
788
+ value: contextValue,
789
+ children: /* @__PURE__ */ jsxs("form", {
790
+ className: cn("relative rounded-xl border border-input bg-background", className),
791
+ onDragEnter: handleDragEnter,
792
+ onDragLeave: handleDragLeave,
793
+ onDragOver: handleDragOver,
794
+ onDrop: handleDrop,
795
+ onPaste: handlePaste,
796
+ onSubmit: handleSubmit,
797
+ ...props,
798
+ children: [children, isDraggingFiles && /* @__PURE__ */ jsx("div", {
799
+ className: "pointer-events-none absolute inset-0 z-10 flex items-center justify-center rounded-[inherit] border-2 border-primary bg-background/90 px-4 text-center font-medium text-sm text-foreground",
800
+ children: t("input.dropFiles")
801
+ })]
802
+ })
803
+ });
804
+ };
805
+ const PromptInputBody = ({ children, className, ...props }) => {
806
+ const { files, removeFile, unsupportedFileNames } = usePromptInput();
807
+ const t = useSurveyAssistantTranslation();
808
+ return /* @__PURE__ */ jsxs("div", {
809
+ className: cn("contents", className),
810
+ ...props,
811
+ children: [children, (files.length > 0 || unsupportedFileNames.length > 0) && /* @__PURE__ */ jsxs("div", {
812
+ className: "flex flex-wrap gap-2 px-3 pb-2",
813
+ children: [files.map((file, index) => /* @__PURE__ */ jsxs("div", {
814
+ className: "inline-flex max-w-full items-center gap-2 rounded-md border border-border bg-muted px-2 py-1 text-xs",
815
+ children: [
816
+ /* @__PURE__ */ jsx(FileIcon, { className: "size-3.5 shrink-0 text-muted-foreground" }),
817
+ /* @__PURE__ */ jsx("span", {
818
+ className: "min-w-0 truncate",
819
+ children: file.name
820
+ }),
821
+ /* @__PURE__ */ jsx("span", {
822
+ className: "shrink-0 text-muted-foreground",
823
+ children: formatFileSize(file.size)
824
+ }),
825
+ /* @__PURE__ */ jsxs("button", {
826
+ className: "shrink-0 rounded-sm text-muted-foreground hover:text-foreground",
827
+ onClick: () => removeFile(index),
828
+ type: "button",
829
+ children: [/* @__PURE__ */ jsx(XIcon, { className: "size-3.5" }), /* @__PURE__ */ jsx("span", {
830
+ className: "sr-only",
831
+ children: t("input.removeFile", { filename: file.name })
832
+ })]
833
+ })
834
+ ]
835
+ }, `${file.name}-${file.lastModified}-${index}`)), unsupportedFileNames.length > 0 && /* @__PURE__ */ jsx("div", {
836
+ className: "text-destructive text-xs",
837
+ children: t("input.unsupportedFiles", { filenames: unsupportedFileNames.join(", ") })
838
+ })]
839
+ })]
840
+ });
841
+ };
842
+ const PromptInputTextarea = ({ className, onChange, onKeyDown, ...props }) => {
843
+ const { value, setValue } = usePromptInput();
844
+ return /* @__PURE__ */ jsx("textarea", {
845
+ className: cn("max-h-40 min-h-20 w-full resize-none bg-transparent px-3 py-2 text-sm outline-none placeholder:text-muted-foreground", className),
846
+ onChange: (event) => {
847
+ setValue(event.target.value);
848
+ onChange?.(event);
849
+ },
850
+ onKeyDown: (event) => {
851
+ onKeyDown?.(event);
852
+ if (event.defaultPrevented || event.shiftKey || event.key !== "Enter") return;
853
+ event.preventDefault();
854
+ event.currentTarget.form?.requestSubmit();
855
+ },
856
+ value,
857
+ ...props
858
+ });
859
+ };
860
+ const PromptInputFooter = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
861
+ className: cn("flex items-center gap-2 border-t border-border px-2 py-2", className),
862
+ ...props
863
+ });
864
+ const PromptInputAttachmentButton = ({ accept = SUPPORTED_FILE_ACCEPT, className, disabled, onChange, ...props }) => {
865
+ const { addFiles } = usePromptInput();
866
+ const t = useSurveyAssistantTranslation();
867
+ return /* @__PURE__ */ jsxs("label", {
868
+ 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),
869
+ children: [
870
+ /* @__PURE__ */ jsx(PaperclipIcon, { className: "size-4" }),
871
+ /* @__PURE__ */ jsx("span", {
872
+ className: "sr-only",
873
+ children: t("input.attachFiles")
874
+ }),
875
+ /* @__PURE__ */ jsx("input", {
876
+ accept,
877
+ className: "sr-only",
878
+ disabled,
879
+ multiple: true,
880
+ onChange: (event) => {
881
+ addFiles(event.currentTarget.files);
882
+ onChange?.(event);
883
+ event.currentTarget.value = "";
884
+ },
885
+ type: "file",
886
+ ...props
887
+ })
888
+ ]
889
+ });
890
+ };
891
+ const PromptInputSubmit = ({ className, status, onStop, onClick, ...props }) => {
892
+ const isGenerating = status === "submitted" || status === "streaming";
893
+ const t = useSurveyAssistantTranslation();
894
+ return /* @__PURE__ */ jsxs("button", {
895
+ className: cn("inline-flex size-8 items-center justify-center rounded-full bg-foreground text-background transition-opacity hover:opacity-90 disabled:pointer-events-none disabled:opacity-50", className),
896
+ onClick: (event) => {
897
+ if (isGenerating && onStop) {
898
+ event.preventDefault();
899
+ onStop();
900
+ return;
901
+ }
902
+ onClick?.(event);
903
+ },
904
+ type: "submit",
905
+ ...props,
906
+ children: [isGenerating ? /* @__PURE__ */ jsx(SquareIcon, { className: "size-3.5" }) : /* @__PURE__ */ jsx(ArrowUpIcon, { className: "size-4" }), /* @__PURE__ */ jsx("span", {
907
+ className: "sr-only",
908
+ children: isGenerating ? t("input.stop") : t("input.send")
909
+ })]
910
+ });
911
+ };
912
+ //#endregion
913
+ //#region src/ui/internal/tool-technical-details.ts
914
+ const formatOmittedLength = (length) => length.toLocaleString("en-US");
915
+ const stringifyToolTechnicalDetails = (value) => JSON.stringify(value, function redactEmbeddedBinary(key, currentValue) {
916
+ const parent = this && typeof this === "object" ? this : void 0;
917
+ if (key === "data" && typeof currentValue === "string" && parent?.type === "embedded" && parent.encoding === "base64") return `[embedded base64 omitted: ${formatOmittedLength(currentValue.length)} characters]`;
918
+ if (typeof currentValue === "string" && currentValue.length > 512 && /^data:[^;,]+;base64,/.test(currentValue)) return `[base64 data URL omitted: ${formatOmittedLength(currentValue.length)} characters]`;
919
+ return currentValue;
920
+ }, 2);
921
+ //#endregion
922
+ //#region src/ui/internal/response.tsx
923
+ const Response = ({ children, className, controls = false, ...props }) => /* @__PURE__ */ jsx(Streamdown, {
924
+ className: cn("leading-6", "[&>:first-child]:mt-0 [&>:last-child]: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", "[&_code]:rounded-md [&_code]:bg-muted [&_code]:px-1 [&_code]:py-0.5 [&_code]:font-mono [&_code]:text-[0.85em]", "[&_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", "[&_pre]:my-3 [&_pre]:overflow-x-auto [&_pre]:rounded-lg [&_pre]:border [&_pre]:bg-muted [&_pre]:p-3", "[&_pre_code]:bg-transparent [&_pre_code]:p-0", "[&_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),
925
+ controls,
926
+ ...props,
927
+ children
928
+ });
929
+ //#endregion
930
+ //#region src/ui/internal/select.tsx
931
+ function Select$1({ ...props }) {
932
+ return /* @__PURE__ */ jsx(Select.Root, {
933
+ "data-slot": "select",
934
+ ...props
935
+ });
936
+ }
937
+ function SelectValue({ ...props }) {
938
+ return /* @__PURE__ */ jsx(Select.Value, {
939
+ "data-slot": "select-value",
940
+ ...props
941
+ });
942
+ }
943
+ function SelectTrigger({ className, size = "default", children, ...props }) {
944
+ return /* @__PURE__ */ jsxs(Select.Trigger, {
945
+ "data-slot": "select-trigger",
946
+ "data-size": size,
947
+ className: cn("flex w-fit items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground", className),
948
+ ...props,
949
+ children: [children, /* @__PURE__ */ jsx(Select.Icon, {
950
+ asChild: true,
951
+ children: /* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-4 opacity-50" })
952
+ })]
953
+ });
954
+ }
955
+ function SelectContent({ className, children, position = "item-aligned", align = "center", ...props }) {
956
+ return /* @__PURE__ */ jsx(Select.Portal, { children: /* @__PURE__ */ jsx("div", {
957
+ className: "case-survey-assistant case-survey-assistant-portal",
958
+ children: /* @__PURE__ */ jsxs(Select.Content, {
959
+ "data-slot": "select-content",
960
+ className: cn("relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95", position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className),
961
+ position,
962
+ align,
963
+ ...props,
964
+ children: [
965
+ /* @__PURE__ */ jsx(SelectScrollUpButton, {}),
966
+ /* @__PURE__ */ jsx(Select.Viewport, {
967
+ className: cn("p-1", position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"),
968
+ children
969
+ }),
970
+ /* @__PURE__ */ jsx(SelectScrollDownButton, {})
971
+ ]
972
+ })
973
+ }) });
974
+ }
975
+ function SelectItem({ className, children, ...props }) {
976
+ return /* @__PURE__ */ jsxs(Select.Item, {
977
+ "data-slot": "select-item",
978
+ className: cn("relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2", className),
979
+ ...props,
980
+ children: [/* @__PURE__ */ jsx("span", {
981
+ "data-slot": "select-item-indicator",
982
+ className: "absolute right-2 flex size-3.5 items-center justify-center",
983
+ children: /* @__PURE__ */ jsx(Select.ItemIndicator, { children: /* @__PURE__ */ jsx(CheckIcon, { className: "size-4" }) })
984
+ }), /* @__PURE__ */ jsx(Select.ItemText, { children })]
985
+ });
986
+ }
987
+ function SelectScrollUpButton({ className, ...props }) {
988
+ return /* @__PURE__ */ jsx(Select.ScrollUpButton, {
989
+ "data-slot": "select-scroll-up-button",
990
+ className: cn("flex cursor-default items-center justify-center py-1", className),
991
+ ...props,
992
+ children: /* @__PURE__ */ jsx(ChevronUpIcon, { className: "size-4" })
993
+ });
994
+ }
995
+ function SelectScrollDownButton({ className, ...props }) {
996
+ return /* @__PURE__ */ jsx(Select.ScrollDownButton, {
997
+ "data-slot": "select-scroll-down-button",
998
+ className: cn("flex cursor-default items-center justify-center py-1", className),
999
+ ...props,
1000
+ children: /* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-4" })
1001
+ });
1002
+ }
1003
+ //#endregion
1004
+ //#region src/ui/internal/code-block.tsx
1005
+ const isItalic = (fontStyle) => fontStyle && fontStyle & 1;
1006
+ const isBold = (fontStyle) => fontStyle && fontStyle & 2;
1007
+ const isUnderline = (fontStyle) => fontStyle && fontStyle & 4;
1008
+ const addKeysToTokens = (lines) => lines.map((line, lineIdx) => ({
1009
+ key: `line-${lineIdx}`,
1010
+ tokens: line.map((token, tokenIdx) => ({
1011
+ key: `line-${lineIdx}-${tokenIdx}`,
1012
+ token
1013
+ }))
1014
+ }));
1015
+ const TokenSpan = ({ token }) => /* @__PURE__ */ jsx("span", {
1016
+ className: "dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]",
1017
+ style: {
1018
+ backgroundColor: token.bgColor,
1019
+ color: token.color,
1020
+ fontStyle: isItalic(token.fontStyle) ? "italic" : void 0,
1021
+ fontWeight: isBold(token.fontStyle) ? "bold" : void 0,
1022
+ textDecoration: isUnderline(token.fontStyle) ? "underline" : void 0,
1023
+ ...token.htmlStyle
1024
+ },
1025
+ children: token.content
1026
+ });
1027
+ const LINE_NUMBER_CLASSES = cn("block", "before:content-[counter(line)]", "before:inline-block", "before:[counter-increment:line]", "before:w-8", "before:mr-4", "before:text-right", "before:text-muted-foreground/50", "before:font-mono", "before:select-none");
1028
+ const LineSpan = ({ keyedLine, showLineNumbers }) => /* @__PURE__ */ jsx("span", {
1029
+ className: showLineNumbers ? LINE_NUMBER_CLASSES : "block",
1030
+ children: keyedLine.tokens.length === 0 ? "\n" : keyedLine.tokens.map(({ token, key }) => /* @__PURE__ */ jsx(TokenSpan, { token }, key))
1031
+ });
1032
+ const CodeBlockContext = createContext({ code: "" });
1033
+ const highlighterCache = /* @__PURE__ */ new Map();
1034
+ const tokensCache = /* @__PURE__ */ new Map();
1035
+ const subscribers = /* @__PURE__ */ new Map();
1036
+ const getTokensCacheKey = (code, language) => {
1037
+ const start = code.slice(0, 100);
1038
+ const end = code.length > 100 ? code.slice(-100) : "";
1039
+ return `${language}:${code.length}:${start}:${end}`;
1040
+ };
1041
+ const getHighlighter = (language) => {
1042
+ const cached = highlighterCache.get(language);
1043
+ if (cached) return cached;
1044
+ const highlighterPromise = createHighlighter({
1045
+ langs: [language],
1046
+ themes: ["github-light", "github-dark"]
1047
+ });
1048
+ highlighterCache.set(language, highlighterPromise);
1049
+ return highlighterPromise;
1050
+ };
1051
+ const createRawTokens = (code) => ({
1052
+ bg: "transparent",
1053
+ fg: "inherit",
1054
+ tokens: code.split("\n").map((line) => line === "" ? [] : [{
1055
+ color: "inherit",
1056
+ content: line
1057
+ }])
1058
+ });
1059
+ const highlightCode = (code, language, callback) => {
1060
+ const tokensCacheKey = getTokensCacheKey(code, language);
1061
+ const cached = tokensCache.get(tokensCacheKey);
1062
+ if (cached) return cached;
1063
+ if (callback) {
1064
+ if (!subscribers.has(tokensCacheKey)) subscribers.set(tokensCacheKey, /* @__PURE__ */ new Set());
1065
+ subscribers.get(tokensCacheKey)?.add(callback);
1066
+ }
1067
+ getHighlighter(language).then((highlighter) => {
1068
+ const langToUse = highlighter.getLoadedLanguages().includes(language) ? language : "text";
1069
+ const result = highlighter.codeToTokens(code, {
1070
+ lang: langToUse,
1071
+ themes: {
1072
+ dark: "github-dark",
1073
+ light: "github-light"
1074
+ }
1075
+ });
1076
+ const tokenized = {
1077
+ bg: result.bg ?? "transparent",
1078
+ fg: result.fg ?? "inherit",
1079
+ tokens: result.tokens
1080
+ };
1081
+ tokensCache.set(tokensCacheKey, tokenized);
1082
+ const subs = subscribers.get(tokensCacheKey);
1083
+ if (subs) {
1084
+ for (const sub of subs) sub(tokenized);
1085
+ subscribers.delete(tokensCacheKey);
1086
+ }
1087
+ }).catch((error) => {
1088
+ console.error("Failed to highlight code:", error);
1089
+ subscribers.delete(tokensCacheKey);
1090
+ });
1091
+ return null;
1092
+ };
1093
+ const CodeBlockBody = memo(({ tokenized, showLineNumbers, className }) => {
1094
+ const preStyle = useMemo(() => ({
1095
+ backgroundColor: tokenized.bg,
1096
+ color: tokenized.fg
1097
+ }), [tokenized.bg, tokenized.fg]);
1098
+ const keyedLines = useMemo(() => addKeysToTokens(tokenized.tokens), [tokenized.tokens]);
1099
+ return /* @__PURE__ */ jsx("pre", {
1100
+ className: cn("dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)] m-0 p-4 text-sm", className),
1101
+ style: preStyle,
1102
+ children: /* @__PURE__ */ jsx("code", {
1103
+ className: cn("font-mono text-sm", showLineNumbers && "[counter-increment:line_0] [counter-reset:line]"),
1104
+ children: keyedLines.map((keyedLine) => /* @__PURE__ */ jsx(LineSpan, {
1105
+ keyedLine,
1106
+ showLineNumbers
1107
+ }, keyedLine.key))
1108
+ })
1109
+ });
1110
+ }, (prevProps, nextProps) => prevProps.tokenized === nextProps.tokenized && prevProps.showLineNumbers === nextProps.showLineNumbers && prevProps.className === nextProps.className);
1111
+ CodeBlockBody.displayName = "CodeBlockBody";
1112
+ const CodeBlockContainer = ({ className, language, style, ...props }) => /* @__PURE__ */ jsx("div", {
1113
+ className: cn("group relative w-full overflow-hidden rounded-md border bg-background text-foreground", className),
1114
+ "data-language": language,
1115
+ style: {
1116
+ containIntrinsicSize: "auto 200px",
1117
+ contentVisibility: "auto",
1118
+ ...style
1119
+ },
1120
+ ...props
1121
+ });
1122
+ const CodeBlockContent = ({ code, language, showLineNumbers = false }) => {
1123
+ const rawTokens = useMemo(() => createRawTokens(code), [code]);
1124
+ const syncTokens = useMemo(() => highlightCode(code, language) ?? rawTokens, [
1125
+ code,
1126
+ language,
1127
+ rawTokens
1128
+ ]);
1129
+ const [asyncTokens, setAsyncTokens] = useState(null);
1130
+ const asyncKeyRef = useRef({
1131
+ code,
1132
+ language
1133
+ });
1134
+ if (asyncKeyRef.current.code !== code || asyncKeyRef.current.language !== language) {
1135
+ asyncKeyRef.current = {
1136
+ code,
1137
+ language
1138
+ };
1139
+ setAsyncTokens(null);
1140
+ }
1141
+ useEffect(() => {
1142
+ let cancelled = false;
1143
+ highlightCode(code, language, (result) => {
1144
+ if (!cancelled) setAsyncTokens(result);
1145
+ });
1146
+ return () => {
1147
+ cancelled = true;
1148
+ };
1149
+ }, [code, language]);
1150
+ return /* @__PURE__ */ jsx("div", {
1151
+ className: "relative overflow-auto",
1152
+ children: /* @__PURE__ */ jsx(CodeBlockBody, {
1153
+ showLineNumbers,
1154
+ tokenized: asyncTokens ?? syncTokens
1155
+ })
1156
+ });
1157
+ };
1158
+ const CodeBlock = ({ code, language, showLineNumbers = false, className, children, ...props }) => {
1159
+ const contextValue = useMemo(() => ({ code }), [code]);
1160
+ return /* @__PURE__ */ jsx(CodeBlockContext.Provider, {
1161
+ value: contextValue,
1162
+ children: /* @__PURE__ */ jsxs(CodeBlockContainer, {
1163
+ className,
1164
+ language,
1165
+ ...props,
1166
+ children: [children, /* @__PURE__ */ jsx(CodeBlockContent, {
1167
+ code,
1168
+ language,
1169
+ showLineNumbers
1170
+ })]
1171
+ })
1172
+ });
1173
+ };
1174
+ //#endregion
1175
+ //#region src/ui/internal/message-timing.ts
1176
+ const isRecord$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1177
+ const asTimestamp = (value) => {
1178
+ if (value instanceof Date) return Number.isFinite(value.getTime()) ? value.getTime() : void 0;
1179
+ if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
1180
+ if (typeof value !== "string") return void 0;
1181
+ const timestamp = Date.parse(value);
1182
+ return Number.isFinite(timestamp) ? timestamp : void 0;
1183
+ };
1184
+ const getMastraCreatedAt = (value) => {
1185
+ if (!isRecord$1(value) || !isRecord$1(value.mastra)) return void 0;
1186
+ return asTimestamp(value.mastra.createdAt);
1187
+ };
1188
+ const getPartTimestamps = (part) => {
1189
+ const value = part;
1190
+ return [
1191
+ getMastraCreatedAt(value.providerMetadata),
1192
+ getMastraCreatedAt(value.callProviderMetadata),
1193
+ getMastraCreatedAt(value.resultProviderMetadata)
1194
+ ].filter((timestamp) => timestamp !== void 0);
1195
+ };
1196
+ /**
1197
+ * Mastra persists message and part timestamps already. Reconstructing elapsed
1198
+ * time here keeps restored chats informative without a second timing store.
1199
+ */
1200
+ const getPersistedMessageDurationSeconds = (message) => {
1201
+ if (!isRecord$1(message.metadata)) return void 0;
1202
+ const startedAt = asTimestamp(message.metadata.createdAt);
1203
+ if (startedAt === void 0) return void 0;
1204
+ const completedAt = Math.max(...message.parts.flatMap(getPartTimestamps));
1205
+ if (!Number.isFinite(completedAt) || completedAt < startedAt) return void 0;
1206
+ return Math.max(1, Math.ceil((completedAt - startedAt) / 1e3));
1207
+ };
1208
+ //#endregion
1209
+ //#region src/ui/survey-assistant-panel.tsx
1210
+ const getAssistantQuickPrompts = (t) => [
1211
+ {
1212
+ label: t("quickActions.review.label"),
1213
+ description: t("quickActions.review.description"),
1214
+ icon: ListChecksIcon,
1215
+ prompt: t("quickActions.review.prompt")
1216
+ },
1217
+ {
1218
+ label: t("quickActions.create.label"),
1219
+ description: t("quickActions.create.description"),
1220
+ icon: MessageSquarePlusIcon,
1221
+ prompt: t("quickActions.create.prompt")
1222
+ },
1223
+ {
1224
+ label: t("quickActions.refine.label"),
1225
+ description: t("quickActions.refine.description"),
1226
+ icon: WandSparklesIcon,
1227
+ prompt: t("quickActions.refine.prompt")
1228
+ }
1229
+ ];
1230
+ const hasRenderableParts = (message) => {
1231
+ return message.parts.some((part) => part.type === "text" && part.text.length > 0 || isFileUIPart(part) || isReasoningUIPart(part) && part.text.trim().length > 0 || isToolUIPart(part));
1232
+ };
1233
+ const getFilePartLabel = (part) => part.filename ?? part.mediaType;
1234
+ const getToolOutput = (part) => part.state === "output-available" ? part.output : void 0;
1235
+ const getToolErrorText = (part) => part.state === "output-error" ? part.errorText : void 0;
1236
+ const localizedToolNames = new Set([
1237
+ "survey_inspect",
1238
+ "survey_change",
1239
+ "survey_expression",
1240
+ "survey_authoring_reference",
1241
+ "survey_attachment",
1242
+ "recall"
1243
+ ]);
1244
+ const getToolDisplayName = (t, toolName, phase = "complete") => localizedToolNames.has(toolName) ? t(`activity.tools.${toolName}.${phase}`) : t(phase === "active" ? "activity.genericActive" : "activity.genericComplete");
1245
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1246
+ const unwrapOutput = unwrapSurveyAssistantToolOutput;
1247
+ const getAssistantErrorPresentation = (error, t) => {
1248
+ const parsed = parseSurveyAssistantError(error);
1249
+ return {
1250
+ title: t(`errors.${parsed.code}.title`),
1251
+ description: parsed.detail ?? t("errors.fallbackDetail"),
1252
+ hint: t(`errors.${parsed.code}.hint`),
1253
+ tone: parsed.code === "model_unavailable" || parsed.code === "rate_limited" ? "warning" : "error"
1254
+ };
1255
+ };
1256
+ const getChangeSet = parseSurveyChangeSet;
1257
+ const isChangeSet = (output) => {
1258
+ return getChangeSet(output) !== null;
1259
+ };
1260
+ const getValidationCounts = (validation) => {
1261
+ if (!isRecord(validation)) return {
1262
+ issueCount: 0,
1263
+ warningCount: 0
1264
+ };
1265
+ return {
1266
+ issueCount: Array.isArray(validation.issues) ? validation.issues.length : 0,
1267
+ warningCount: Array.isArray(validation.warnings) ? validation.warnings.length : 0
1268
+ };
1269
+ };
1270
+ const SurveyAssistantToolOutputSummary = ({ applyResult, output }) => {
1271
+ const t = useSurveyAssistantTranslation();
1272
+ const unwrappedOutput = unwrapOutput(output);
1273
+ if (isChangeSet(unwrappedOutput)) {
1274
+ const { issueCount, warningCount } = getValidationCounts(unwrappedOutput.validation);
1275
+ const isValid = unwrappedOutput.status === "validated";
1276
+ const status = applyResult?.status ?? (isValid ? "validated" : "rejected");
1277
+ const isPositiveStatus = status === "applied" || status === "validated";
1278
+ const statusLabel = status === "applied" ? t("changes.applied") : status === "stale" ? t("changes.stale") : status === "failed" ? t("changes.failed") : isValid ? t("changes.prepared") : t("changes.rejected");
1279
+ const statusMessage = applyResult?.message ?? unwrappedOutput.summary;
1280
+ return /* @__PURE__ */ jsxs("div", {
1281
+ className: cn("relative rounded-md border px-2.5 py-2 text-xs", isPositiveStatus ? "border-emerald-200/80 bg-emerald-50/60 text-emerald-950" : status === "stale" ? "border-amber-200 bg-amber-50 text-amber-900" : "border-red-200 bg-red-50 text-red-900"),
1282
+ children: [
1283
+ /* @__PURE__ */ jsxs("div", {
1284
+ className: "flex items-center gap-2 font-medium",
1285
+ children: [isPositiveStatus ? /* @__PURE__ */ jsx(CheckCircleIcon, { className: "size-3.5 shrink-0" }) : /* @__PURE__ */ jsx(XCircleIcon, { className: "size-3.5 shrink-0" }), /* @__PURE__ */ jsx("span", { children: statusLabel })]
1286
+ }),
1287
+ /* @__PURE__ */ jsxs("div", {
1288
+ className: "mt-1 text-xs",
1289
+ children: [statusMessage, unwrappedOutput.operations.length > 0 && /* @__PURE__ */ jsxs("span", {
1290
+ className: "text-current/70",
1291
+ children: [
1292
+ " ",
1293
+ "(",
1294
+ t(unwrappedOutput.operations.length === 1 ? "changes.operationCount" : "changes.operationCountPlural", { count: unwrappedOutput.operations.length }),
1295
+ ")"
1296
+ ]
1297
+ })]
1298
+ }),
1299
+ (issueCount > 0 || warningCount > 0) && /* @__PURE__ */ jsxs("div", {
1300
+ className: "mt-1 text-current/70",
1301
+ children: [
1302
+ issueCount > 0 && t(issueCount === 1 ? "changes.issueCount" : "changes.issueCountPlural", { count: issueCount }),
1303
+ issueCount > 0 && warningCount > 0 && ", ",
1304
+ warningCount > 0 && t(warningCount === 1 ? "changes.warningCount" : "changes.warningCountPlural", { count: warningCount })
1305
+ ]
1306
+ })
1307
+ ]
1308
+ });
1309
+ }
1310
+ return null;
1311
+ };
1312
+ const getToolTitle = (t, toolName, state) => {
1313
+ if (state === "output-available") return getToolDisplayName(t, toolName, "complete");
1314
+ if (state === "output-error") return t("activity.failedSuffix", { name: getToolDisplayName(t, toolName, "complete") });
1315
+ if (state === "output-denied") return t("activity.skippedSuffix", { name: getToolDisplayName(t, toolName, "active") });
1316
+ return getToolDisplayName(t, toolName, "active");
1317
+ };
1318
+ const getToolProgressLabel = (t, part) => {
1319
+ switch (part.state) {
1320
+ case "approval-requested": return t("activity.waitingApproval");
1321
+ case "approval-responded": return t("activity.continuingApproval");
1322
+ case "input-streaming": return t("activity.preparingStep");
1323
+ case "input-available": return getToolDisplayName(t, getToolName(part), "active");
1324
+ case "output-available": return t("activity.usingResult");
1325
+ case "output-denied": return t("activity.continuingWithoutStep");
1326
+ case "output-error": return t("activity.recoveringToolError");
1327
+ }
1328
+ };
1329
+ const getToolStatusMeta = (t) => ({
1330
+ "approval-requested": {
1331
+ icon: /* @__PURE__ */ jsx(ClockIcon, { className: "size-3 text-amber-600" }),
1332
+ label: t("activity.waitingApproval"),
1333
+ stepStatus: "active"
1334
+ },
1335
+ "approval-responded": {
1336
+ icon: /* @__PURE__ */ jsx(CheckCircleIcon, { className: "size-3 text-blue-600" }),
1337
+ label: t("activity.approvalReceived"),
1338
+ stepStatus: "complete"
1339
+ },
1340
+ "input-available": {
1341
+ icon: /* @__PURE__ */ jsx(LoaderCircleIcon, { className: "size-3 animate-spin text-blue-600" }),
1342
+ label: null,
1343
+ stepStatus: "active"
1344
+ },
1345
+ "input-streaming": {
1346
+ icon: /* @__PURE__ */ jsx(LoaderCircleIcon, { className: "size-3 animate-spin text-blue-600" }),
1347
+ label: null,
1348
+ stepStatus: "active"
1349
+ },
1350
+ "output-available": {
1351
+ icon: /* @__PURE__ */ jsx(CheckCircleIcon, { className: "size-3 text-emerald-600" }),
1352
+ label: null,
1353
+ stepStatus: "complete"
1354
+ },
1355
+ "output-denied": {
1356
+ icon: /* @__PURE__ */ jsx(XCircleIcon, { className: "size-3 text-orange-600" }),
1357
+ label: null,
1358
+ stepStatus: "complete"
1359
+ },
1360
+ "output-error": {
1361
+ icon: /* @__PURE__ */ jsx(XCircleIcon, { className: "size-3 text-red-600" }),
1362
+ label: null,
1363
+ stepStatus: "complete"
1364
+ }
1365
+ });
1366
+ const getStoppedToolStatusMeta = (t) => ({
1367
+ icon: /* @__PURE__ */ jsx(XCircleIcon, { className: "size-3 text-muted-foreground" }),
1368
+ label: t("activity.stopped"),
1369
+ stepStatus: "complete"
1370
+ });
1371
+ const SurveyAssistantToolStep = ({ applyResultsByChangeId, isAborted, part, isLast }) => {
1372
+ const t = useSurveyAssistantTranslation();
1373
+ const toolStatusMeta = getToolStatusMeta(t);
1374
+ const toolName = getToolName(part);
1375
+ const output = getToolOutput(part);
1376
+ const errorText = getToolErrorText(part);
1377
+ const changeSet = output === void 0 ? null : getChangeSet(output);
1378
+ const applyResult = changeSet ? applyResultsByChangeId[changeSet.id] : void 0;
1379
+ const title = changeSet ? toolName === "survey_expression" ? t("activity.expressionChange") : t("activity.surveyChange") : getToolTitle(t, toolName, part.state);
1380
+ const meta = isAborted && toolStatusMeta[part.state].stepStatus === "active" ? getStoppedToolStatusMeta(t) : toolStatusMeta[part.state];
1381
+ const metaTitle = meta.label ?? (part.state === "output-denied" ? t("activity.skipped") : part.state === "output-error" ? t("activity.failed") : meta.stepStatus === "active" ? t("activity.workingShort") : t("activity.done"));
1382
+ const [detailsOpen, setDetailsOpen] = useState(part.state === "output-error");
1383
+ const hasDetails = part.input !== void 0 || output !== void 0 || Boolean(errorText);
1384
+ return /* @__PURE__ */ jsx(ChainOfThoughtStep, {
1385
+ icon: WrenchIcon,
1386
+ isLast,
1387
+ label: /* @__PURE__ */ jsxs("span", {
1388
+ className: "flex flex-wrap items-center gap-x-2 gap-y-0.5",
1389
+ children: [/* @__PURE__ */ jsx("span", { children: title }), /* @__PURE__ */ jsxs("span", {
1390
+ className: cn("inline-flex shrink-0 items-center gap-1 text-[11px] font-normal", meta.stepStatus === "active" ? "text-blue-700" : "text-muted-foreground"),
1391
+ title: metaTitle,
1392
+ children: [
1393
+ meta.icon,
1394
+ meta.label && /* @__PURE__ */ jsx("span", { children: meta.label }),
1395
+ !meta.label && /* @__PURE__ */ jsx("span", {
1396
+ className: "sr-only",
1397
+ children: metaTitle
1398
+ })
1399
+ ]
1400
+ })]
1401
+ }),
1402
+ result: output !== void 0 ? /* @__PURE__ */ jsx(SurveyAssistantToolOutputSummary, {
1403
+ applyResult,
1404
+ output
1405
+ }) : void 0,
1406
+ status: meta.stepStatus,
1407
+ children: hasDetails && /* @__PURE__ */ jsxs(Collapsible$1, {
1408
+ onOpenChange: setDetailsOpen,
1409
+ open: detailsOpen,
1410
+ children: [/* @__PURE__ */ jsxs(CollapsibleTrigger, {
1411
+ className: "flex items-center gap-1 text-muted-foreground text-xs hover:text-foreground",
1412
+ children: [/* @__PURE__ */ jsx(ChevronRightIcon, { className: cn("size-3 transition-transform", detailsOpen && "rotate-90") }), t(detailsOpen ? "activity.hideTechnicalDetails" : "activity.technicalDetails")]
1413
+ }), /* @__PURE__ */ jsxs(CollapsibleContent, {
1414
+ className: "mt-2 space-y-2",
1415
+ children: [part.input !== void 0 && /* @__PURE__ */ jsx(CodeBlock, {
1416
+ className: "border-border/50 bg-muted/30",
1417
+ code: stringifyToolTechnicalDetails(part.input),
1418
+ language: "json"
1419
+ }), (output !== void 0 || errorText) && /* @__PURE__ */ jsx(CodeBlock, {
1420
+ className: cn("bg-muted/30", errorText ? "border-destructive/30" : "border-border/50"),
1421
+ code: errorText ?? stringifyToolTechnicalDetails(output),
1422
+ language: "json"
1423
+ })]
1424
+ })]
1425
+ })
1426
+ });
1427
+ };
1428
+ const getStreamingProgressLabel = (t, message) => {
1429
+ if (!message || message.role !== "assistant") return t("status.connecting");
1430
+ const lastPart = message.parts.at(-1);
1431
+ if (!lastPart) return t("status.thinking");
1432
+ if (isReasoningUIPart(lastPart)) return t("status.thinking");
1433
+ if (isToolUIPart(lastPart)) return getToolProgressLabel(t, lastPart);
1434
+ if (lastPart.type === "text") return t("status.writing");
1435
+ return t("status.working");
1436
+ };
1437
+ const SurveyAssistantProgressIndicator = ({ message }) => {
1438
+ const t = useSurveyAssistantTranslation();
1439
+ return /* @__PURE__ */ jsxs("div", {
1440
+ "aria-live": "polite",
1441
+ className: "mt-2 flex items-center gap-2 px-1 text-muted-foreground text-xs",
1442
+ role: "status",
1443
+ children: [/* @__PURE__ */ jsx(LoaderCircleIcon, { className: "size-3.5 animate-spin text-blue-600" }), /* @__PURE__ */ jsx("span", { children: getStreamingProgressLabel(t, message) })]
1444
+ });
1445
+ };
1446
+ const buildMessageBlocks = (parts) => {
1447
+ const blocks = [];
1448
+ let pendingEntries = [];
1449
+ const flushChain = () => {
1450
+ if (pendingEntries.length > 0) {
1451
+ blocks.push({
1452
+ type: "chain",
1453
+ key: `chain-${pendingEntries[0].key}`,
1454
+ entries: pendingEntries
1455
+ });
1456
+ pendingEntries = [];
1457
+ }
1458
+ };
1459
+ parts.forEach((part, index) => {
1460
+ if (isReasoningUIPart(part)) {
1461
+ if (part.text) pendingEntries.push({
1462
+ type: "reasoning",
1463
+ key: `reasoning-${index}`,
1464
+ text: part.text
1465
+ });
1466
+ return;
1467
+ }
1468
+ if (isToolUIPart(part)) {
1469
+ pendingEntries.push({
1470
+ type: "tool",
1471
+ key: `tool-${part.toolCallId}`,
1472
+ part
1473
+ });
1474
+ return;
1475
+ }
1476
+ if (part.type === "text") {
1477
+ flushChain();
1478
+ blocks.push({
1479
+ type: "text",
1480
+ key: `text-${index}`,
1481
+ text: part.text
1482
+ });
1483
+ return;
1484
+ }
1485
+ if (isFileUIPart(part)) {
1486
+ flushChain();
1487
+ blocks.push({
1488
+ type: "file",
1489
+ key: `file-${index}`,
1490
+ part
1491
+ });
1492
+ }
1493
+ });
1494
+ flushChain();
1495
+ return blocks;
1496
+ };
1497
+ const SurveyAssistantMessageParts = ({ applyResultsByChangeId, isAborted, isLastMessage, isStreaming, message, showProgress }) => {
1498
+ const blocks = useMemo(() => buildMessageBlocks(message.parts), [message.parts]);
1499
+ const persistedDuration = useMemo(() => getPersistedMessageDurationSeconds(message), [message]);
1500
+ const lastPart = message.parts.at(-1);
1501
+ const hasActiveToolMessage = lastPart !== void 0 && isToolUIPart(lastPart) && (lastPart.state === "input-streaming" || lastPart.state === "input-available");
1502
+ return /* @__PURE__ */ jsxs(Fragment, { children: [blocks.map((block, blockIndex) => {
1503
+ const isLastBlock = blockIndex === blocks.length - 1;
1504
+ if (block.type === "text") {
1505
+ if (message.role === "assistant") return /* @__PURE__ */ jsx(Response, {
1506
+ className: "leading-5 [&_li]:my-0.5 [&_p]:my-1.5 [&_p:first-child]:mt-0 [&_p:last-child]:mb-0",
1507
+ children: block.text
1508
+ }, block.key);
1509
+ return /* @__PURE__ */ jsx("span", { children: block.text }, block.key);
1510
+ }
1511
+ if (block.type === "file") return /* @__PURE__ */ jsxs("a", {
1512
+ className: "inline-flex max-w-full items-center gap-2 rounded-md border border-border bg-muted px-2 py-1 text-xs hover:bg-muted/80",
1513
+ href: block.part.url,
1514
+ rel: "noreferrer",
1515
+ target: "_blank",
1516
+ children: [
1517
+ /* @__PURE__ */ jsx(FileIcon, { className: "size-3.5 shrink-0 text-muted-foreground" }),
1518
+ /* @__PURE__ */ jsx("span", {
1519
+ className: "min-w-0 truncate",
1520
+ children: getFilePartLabel(block.part)
1521
+ }),
1522
+ /* @__PURE__ */ jsx("span", {
1523
+ className: "shrink-0 text-muted-foreground",
1524
+ children: block.part.mediaType
1525
+ })
1526
+ ]
1527
+ }, block.key);
1528
+ const isChainActive = isLastMessage && isStreaming && isLastBlock;
1529
+ return /* @__PURE__ */ jsxs(ChainOfThought, {
1530
+ autoClose: true,
1531
+ className: "w-full rounded-md bg-muted/35 px-2 py-1.5",
1532
+ defaultDuration: persistedDuration,
1533
+ defaultOpen: isChainActive,
1534
+ isStreaming: isChainActive,
1535
+ children: [/* @__PURE__ */ jsx(ChainOfThoughtHeader, { className: "text-xs font-medium" }), /* @__PURE__ */ jsxs(ChainOfThoughtContent, {
1536
+ className: "relative mt-2",
1537
+ children: [/* @__PURE__ */ jsx("div", {
1538
+ "aria-hidden": "true",
1539
+ className: "absolute top-0 bottom-0 left-[7px] w-px bg-border"
1540
+ }), block.entries.map((entry, index) => {
1541
+ const isLastEntry = index === block.entries.length - 1;
1542
+ if (entry.type === "reasoning") return /* @__PURE__ */ jsx(Response, {
1543
+ className: cn("pr-1 pl-5 text-muted-foreground text-xs leading-5 [&_p]:my-1 [&_p:first-child]:mt-0 [&_p:last-child]:mb-0", !isLastEntry && "mb-2"),
1544
+ children: entry.text
1545
+ }, entry.key);
1546
+ return /* @__PURE__ */ jsx(SurveyAssistantToolStep, {
1547
+ applyResultsByChangeId,
1548
+ isAborted,
1549
+ isLast: isLastEntry,
1550
+ part: entry.part
1551
+ }, entry.key);
1552
+ })]
1553
+ })]
1554
+ }, block.key);
1555
+ }), showProgress && !hasActiveToolMessage && (lastPart === void 0 || !isReasoningUIPart(lastPart)) && /* @__PURE__ */ jsx(SurveyAssistantProgressIndicator, { message })] });
1556
+ };
1557
+ const SurveyAssistantTurnOutcomeNotice = (outcome) => {
1558
+ const t = useSurveyAssistantTranslation();
1559
+ const description = outcome.status === "blocked" ? t(outcome.blockedChangeSetCount === 1 ? "changes.blocked" : "changes.blockedPlural", { count: outcome.blockedChangeSetCount }) : t(outcome.appliedChangeSetCount === 1 ? "changes.partial" : "changes.partialPlural", {
1560
+ applied: outcome.appliedChangeSetCount,
1561
+ failed: outcome.failedChangeSetCount
1562
+ });
1563
+ return /* @__PURE__ */ jsxs("div", {
1564
+ className: "rounded-lg border border-amber-200 bg-amber-50 px-3 py-2 text-amber-950 text-xs",
1565
+ children: [/* @__PURE__ */ jsx("div", {
1566
+ className: "font-medium",
1567
+ children: t("changes.incompleteTitle")
1568
+ }), /* @__PURE__ */ jsxs("div", {
1569
+ className: "mt-1",
1570
+ children: [
1571
+ description,
1572
+ " ",
1573
+ t("changes.reviewBeforeRetry")
1574
+ ]
1575
+ })]
1576
+ });
1577
+ };
1578
+ const SurveyAssistantHeaderIdentity = ({ indicatorClassName, status, title }) => /* @__PURE__ */ jsxs("div", {
1579
+ className: "flex min-w-0 items-center gap-2",
1580
+ children: [/* @__PURE__ */ jsxs("div", {
1581
+ className: "relative flex size-6 shrink-0 items-center justify-center rounded-md bg-foreground text-background shadow-sm",
1582
+ children: [/* @__PURE__ */ jsx(SparklesIcon, { className: "size-3.5" }), /* @__PURE__ */ jsx("span", {
1583
+ "aria-hidden": "true",
1584
+ className: cn("absolute -right-px -bottom-px size-1.5 rounded-full border border-background", indicatorClassName)
1585
+ })]
1586
+ }), /* @__PURE__ */ jsxs("div", {
1587
+ className: "min-w-0",
1588
+ children: [/* @__PURE__ */ jsx("h2", {
1589
+ className: "truncate font-semibold text-[13px] leading-4",
1590
+ children: title
1591
+ }), /* @__PURE__ */ jsx("span", {
1592
+ "aria-live": "polite",
1593
+ className: "block truncate text-[10px] text-muted-foreground leading-3",
1594
+ role: "status",
1595
+ children: status
1596
+ })]
1597
+ })]
1598
+ });
1599
+ const SurveyAssistantChatPanel = ({ api, applyPolicy = "auto", controller, embedded = false, initialMessages, modelId, models, onModelChange, onFirstSubmit, onTurnSettled, onApplyReceipts, onReviewChangeSets, onTurnActiveChange, resourceId, threadControls, threadId, threadTitle }) => {
1600
+ const t = useSurveyAssistantTranslation();
1601
+ const assistantQuickPrompts = getAssistantQuickPrompts(t);
1602
+ const { applyResultsByChangeId, authorizationError, dismissError, error, isStreaming, latestTurnOutcome, messages, state, status, stop, submit, retry } = useSurveyAssistantSession({
1603
+ api,
1604
+ applyPolicy,
1605
+ controller,
1606
+ initialMessages,
1607
+ modelId,
1608
+ onApplyReceipts,
1609
+ onReviewChangeSets,
1610
+ onTurnActiveChange,
1611
+ resourceId,
1612
+ threadId
1613
+ });
1614
+ const showAssistantError = Boolean(authorizationError) || Boolean(error);
1615
+ const assistantErrorPresentation = getAssistantErrorPresentation(authorizationError ?? error, t);
1616
+ const selectedModelLabel = models.find((model) => model.id === modelId)?.label ?? modelId ?? t("input.defaultModel");
1617
+ const lastMessage = messages.at(-1);
1618
+ const isAssistantTurnActive = isSurveyAssistantTurnActive({
1619
+ isStreaming,
1620
+ state
1621
+ });
1622
+ const turnWasActiveRef = useRef(false);
1623
+ const isAssistantTurnSettled = !isStreaming && (state === "completed" || state === "failed" || state === "aborted" || state === "applied");
1624
+ useEffect(() => {
1625
+ if (isAssistantTurnActive) {
1626
+ turnWasActiveRef.current = true;
1627
+ return;
1628
+ }
1629
+ if (!turnWasActiveRef.current || !isAssistantTurnSettled) return;
1630
+ turnWasActiveRef.current = false;
1631
+ onTurnSettled();
1632
+ }, [
1633
+ isAssistantTurnActive,
1634
+ isAssistantTurnSettled,
1635
+ onTurnSettled
1636
+ ]);
1637
+ const showPendingAssistantRow = isAssistantTurnActive && (!lastMessage || lastMessage.role !== "assistant");
1638
+ const sessionStatusLabel = state === "applying" ? t("status.applying") : state === "applied" ? t("status.applied") : state === "aborted" ? t("status.stopped") : isAssistantTurnActive ? getStreamingProgressLabel(t, lastMessage) : showAssistantError ? t("status.attention") : t("status.ready");
1639
+ const handleSubmit = useCallback((message) => {
1640
+ if (isAssistantTurnActive) return false;
1641
+ const accepted = submit({
1642
+ files: message.files,
1643
+ text: message.text
1644
+ });
1645
+ if (accepted) onFirstSubmit(message.text);
1646
+ return accepted;
1647
+ }, [
1648
+ isAssistantTurnActive,
1649
+ onFirstSubmit,
1650
+ submit
1651
+ ]);
1652
+ const handleQuickPrompt = useCallback((prompt) => handleSubmit({
1653
+ files: [],
1654
+ text: prompt
1655
+ }), [handleSubmit]);
1656
+ return /* @__PURE__ */ jsxs("aside", {
1657
+ "aria-label": t("panel.ariaLabel"),
1658
+ className: cn("flex h-full min-h-0 flex-col overflow-visible bg-background", !embedded && "rounded-xl border border-border shadow-sm"),
1659
+ children: [
1660
+ /* @__PURE__ */ jsxs("header", {
1661
+ className: "box-content flex h-9 shrink-0 items-center justify-between gap-2 border-b border-border bg-muted/20 px-2.5",
1662
+ children: [/* @__PURE__ */ jsx(SurveyAssistantHeaderIdentity, {
1663
+ indicatorClassName: showAssistantError ? "bg-destructive" : isAssistantTurnActive ? "animate-pulse bg-blue-500" : state === "aborted" ? "bg-muted-foreground" : "bg-emerald-500",
1664
+ status: sessionStatusLabel,
1665
+ title: threadTitle
1666
+ }), threadControls]
1667
+ }),
1668
+ /* @__PURE__ */ jsxs(Conversation, {
1669
+ className: "min-h-0",
1670
+ children: [/* @__PURE__ */ jsx(ConversationContent, {
1671
+ className: "gap-3 px-3 py-2.5",
1672
+ children: messages.length === 0 ? /* @__PURE__ */ jsx(ConversationEmptyState, {
1673
+ className: "min-h-full items-stretch justify-center gap-0 px-1 py-6 text-left",
1674
+ children: /* @__PURE__ */ jsxs("div", {
1675
+ className: "mx-auto w-full max-w-sm",
1676
+ children: [
1677
+ /* @__PURE__ */ jsx("div", {
1678
+ className: "mb-3 flex size-10 items-center justify-center rounded-xl bg-foreground text-background shadow-sm",
1679
+ children: /* @__PURE__ */ jsx(SparklesIcon, { className: "size-5" })
1680
+ }),
1681
+ /* @__PURE__ */ jsx("h3", {
1682
+ className: "font-semibold text-foreground text-base tracking-tight",
1683
+ children: t("empty.title")
1684
+ }),
1685
+ /* @__PURE__ */ jsx("p", {
1686
+ className: "mt-1 text-muted-foreground text-xs leading-relaxed",
1687
+ children: t("empty.description")
1688
+ }),
1689
+ /* @__PURE__ */ jsx("div", {
1690
+ className: "mt-4 flex flex-col gap-1.5",
1691
+ children: assistantQuickPrompts.map(({ label, description, icon: Icon, prompt }) => /* @__PURE__ */ jsxs("button", {
1692
+ className: "group flex w-full items-center gap-2.5 rounded-lg border border-border bg-background px-2.5 py-2 text-left transition-colors hover:border-foreground/20 hover:bg-muted/50",
1693
+ onClick: () => handleQuickPrompt(prompt),
1694
+ type: "button",
1695
+ children: [
1696
+ /* @__PURE__ */ jsx("span", {
1697
+ className: "flex size-7 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground transition-colors group-hover:bg-background group-hover:text-foreground",
1698
+ children: /* @__PURE__ */ jsx(Icon, { className: "size-3.5" })
1699
+ }),
1700
+ /* @__PURE__ */ jsxs("span", {
1701
+ className: "min-w-0 flex-1",
1702
+ children: [/* @__PURE__ */ jsx("span", {
1703
+ className: "block truncate font-medium text-foreground text-xs",
1704
+ children: label
1705
+ }), /* @__PURE__ */ jsx("span", {
1706
+ className: "block truncate text-[11px] text-muted-foreground",
1707
+ children: description
1708
+ })]
1709
+ }),
1710
+ /* @__PURE__ */ jsx(ChevronRightIcon, { className: "size-3.5 shrink-0 text-muted-foreground/40 transition-transform group-hover:translate-x-0.5 group-hover:text-muted-foreground" })
1711
+ ]
1712
+ }, label))
1713
+ })
1714
+ ]
1715
+ })
1716
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [messages.map((message, index) => {
1717
+ const isLastMessage = index === messages.length - 1;
1718
+ const shouldShowProgress = isAssistantTurnActive && isLastMessage && message.role === "assistant";
1719
+ return /* @__PURE__ */ jsx(Message, {
1720
+ className: "max-w-[96%] gap-1",
1721
+ from: message.role,
1722
+ children: /* @__PURE__ */ jsx(MessageContent, {
1723
+ className: cn(message.role === "assistant" && "w-full space-y-2"),
1724
+ children: hasRenderableParts(message) ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(SurveyAssistantMessageParts, {
1725
+ applyResultsByChangeId,
1726
+ isAborted: state === "aborted",
1727
+ isLastMessage,
1728
+ isStreaming: isAssistantTurnActive,
1729
+ message,
1730
+ showProgress: shouldShowProgress
1731
+ }), isLastMessage && latestTurnOutcome && /* @__PURE__ */ jsx(SurveyAssistantTurnOutcomeNotice, { ...latestTurnOutcome })] }) : shouldShowProgress ? /* @__PURE__ */ jsx(SurveyAssistantProgressIndicator, { message }) : state === "aborted" ? /* @__PURE__ */ jsx("span", {
1732
+ className: "text-muted-foreground text-sm",
1733
+ children: t("conversation.stopped")
1734
+ }) : /* @__PURE__ */ jsx("span", {
1735
+ className: "text-muted-foreground text-sm",
1736
+ children: t("conversation.working")
1737
+ })
1738
+ })
1739
+ }, message.id);
1740
+ }), showPendingAssistantRow && /* @__PURE__ */ jsx(Message, {
1741
+ className: "max-w-[96%] gap-1",
1742
+ from: "assistant",
1743
+ children: /* @__PURE__ */ jsx(MessageContent, {
1744
+ className: "w-full",
1745
+ children: /* @__PURE__ */ jsx(SurveyAssistantProgressIndicator, {})
1746
+ })
1747
+ })] })
1748
+ }), /* @__PURE__ */ jsx(ConversationScrollButton, { className: "bottom-2.5" })]
1749
+ }),
1750
+ showAssistantError && /* @__PURE__ */ jsxs("div", {
1751
+ className: cn("flex items-start justify-between gap-3 border-t px-3 py-2.5 text-sm", assistantErrorPresentation.tone === "warning" ? "border-amber-200 bg-amber-50/60" : "border-destructive/20 bg-destructive/5"),
1752
+ role: "alert",
1753
+ children: [/* @__PURE__ */ jsxs("div", {
1754
+ className: "flex min-w-0 gap-2.5",
1755
+ children: [assistantErrorPresentation.tone === "warning" ? /* @__PURE__ */ jsx(WrenchIcon, { className: "mt-0.5 size-4 shrink-0 text-amber-700" }) : /* @__PURE__ */ jsx(XCircleIcon, { className: "mt-0.5 size-4 shrink-0 text-destructive" }), /* @__PURE__ */ jsxs("div", {
1756
+ className: "min-w-0",
1757
+ children: [
1758
+ /* @__PURE__ */ jsx("div", {
1759
+ className: "font-medium text-foreground",
1760
+ children: assistantErrorPresentation.title
1761
+ }),
1762
+ /* @__PURE__ */ jsx("p", {
1763
+ className: "mt-0.5 wrap-break-word text-muted-foreground",
1764
+ children: assistantErrorPresentation.description
1765
+ }),
1766
+ /* @__PURE__ */ jsx("p", {
1767
+ className: "mt-1 text-muted-foreground/80",
1768
+ children: assistantErrorPresentation.hint
1769
+ }),
1770
+ /* @__PURE__ */ jsx("button", {
1771
+ className: "mt-1.5 font-medium text-foreground text-xs hover:underline",
1772
+ onClick: retry,
1773
+ type: "button",
1774
+ children: t("errors.retry")
1775
+ })
1776
+ ]
1777
+ })]
1778
+ }), /* @__PURE__ */ jsx("button", {
1779
+ "aria-label": t("errors.dismissAssistant"),
1780
+ className: "shrink-0 text-muted-foreground text-xs hover:text-foreground",
1781
+ onClick: dismissError,
1782
+ type: "button",
1783
+ children: t("errors.dismiss")
1784
+ })]
1785
+ }),
1786
+ /* @__PURE__ */ jsx("div", {
1787
+ className: "shrink-0 border-t border-border bg-muted/20 p-2",
1788
+ children: /* @__PURE__ */ jsxs(PromptInput, {
1789
+ "aria-busy": isAssistantTurnActive,
1790
+ attachmentsDisabled: isAssistantTurnActive,
1791
+ className: cn("rounded-xl border-border bg-background shadow-sm transition-colors", isAssistantTurnActive && "border-border/60 bg-muted/50 shadow-none"),
1792
+ onSubmit: handleSubmit,
1793
+ children: [/* @__PURE__ */ jsx(PromptInputBody, { children: /* @__PURE__ */ jsx(PromptInputTextarea, {
1794
+ className: "min-h-11 max-h-28 px-3 pt-2.5 pb-1.5 transition-colors disabled:cursor-not-allowed disabled:text-muted-foreground disabled:placeholder:text-muted-foreground/60",
1795
+ disabled: isAssistantTurnActive,
1796
+ placeholder: t("input.placeholder")
1797
+ }) }), /* @__PURE__ */ jsxs(PromptInputFooter, {
1798
+ className: cn("justify-between border-0 px-1.5 pt-0 pb-1.5 transition-colors", isAssistantTurnActive && "bg-muted/30"),
1799
+ children: [/* @__PURE__ */ jsx(PromptInputAttachmentButton, {
1800
+ className: "size-8",
1801
+ disabled: isAssistantTurnActive
1802
+ }), /* @__PURE__ */ jsxs("div", {
1803
+ className: "flex min-w-0 items-center justify-end gap-1",
1804
+ children: [models.length > 1 && modelId ? /* @__PURE__ */ jsxs(Select$1, {
1805
+ disabled: isAssistantTurnActive,
1806
+ onValueChange: onModelChange,
1807
+ value: modelId,
1808
+ children: [/* @__PURE__ */ jsxs(SelectTrigger, {
1809
+ "aria-label": t("input.model"),
1810
+ className: "h-8 min-w-0 max-w-48 gap-1.5 border-0 bg-transparent px-2 text-xs shadow-none hover:bg-muted hover:text-foreground focus-visible:ring-1",
1811
+ size: "sm",
1812
+ title: modelId,
1813
+ children: [/* @__PURE__ */ jsx(BotIcon, { className: "size-3.5" }), /* @__PURE__ */ jsx(SelectValue, {})]
1814
+ }), /* @__PURE__ */ jsx(SelectContent, {
1815
+ align: "end",
1816
+ className: "min-w-52 rounded-lg border-border p-0 shadow-lg",
1817
+ position: "popper",
1818
+ children: models.map((model) => /* @__PURE__ */ jsx(SelectItem, {
1819
+ className: "min-h-9 rounded-md px-2 py-2 text-xs focus:bg-muted",
1820
+ title: model.id,
1821
+ value: model.id,
1822
+ children: model.label
1823
+ }, model.id))
1824
+ })]
1825
+ }) : /* @__PURE__ */ jsxs("div", {
1826
+ className: "flex h-8 min-w-0 max-w-48 items-center gap-1.5 px-2 text-muted-foreground text-xs",
1827
+ title: modelId,
1828
+ children: [/* @__PURE__ */ jsx(BotIcon, { className: "size-3.5 shrink-0" }), /* @__PURE__ */ jsx("span", {
1829
+ className: "truncate",
1830
+ children: selectedModelLabel
1831
+ })]
1832
+ }), /* @__PURE__ */ jsx(PromptInputSubmit, {
1833
+ onStop: stop,
1834
+ status
1835
+ })]
1836
+ })]
1837
+ })]
1838
+ })
1839
+ })
1840
+ ]
1841
+ });
1842
+ };
1843
+ const SELECTED_THREAD_STORAGE_KEY = "survey-assistant-selected-thread";
1844
+ const createThreadTitle = (text, fallback) => {
1845
+ const normalized = text.replace(/\s+/g, " ").trim();
1846
+ if (!normalized) return fallback;
1847
+ const words = normalized.split(" ").slice(0, 8).join(" ");
1848
+ return words.length > 64 ? `${words.slice(0, 61).trimEnd()}...` : words;
1849
+ };
1850
+ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], onConfirmDeleteThread, onModelChange = () => {}, resourceId, selectedThreadStorageKey, threadApi, threadClient: providedThreadClient, ...sessionProps }) => {
1851
+ const t = useSurveyAssistantTranslation();
1852
+ const threadClient = useMemo(() => providedThreadClient ?? createSurveyAssistantThreadClient(threadApi), [providedThreadClient, threadApi]);
1853
+ const storageKey = selectedThreadStorageKey === false ? null : selectedThreadStorageKey ?? `${SELECTED_THREAD_STORAGE_KEY}:${encodeURIComponent(resourceId)}`;
1854
+ const [threads, setThreads] = useState([]);
1855
+ const [selectedThread, setSelectedThread] = useState(null);
1856
+ const [initialMessages, setInitialMessages] = useState([]);
1857
+ const [historyOpen, setHistoryOpen] = useState(false);
1858
+ const [editingThreadId, setEditingThreadId] = useState(null);
1859
+ const [editingTitle, setEditingTitle] = useState("");
1860
+ const [threadError, setThreadError] = useState(null);
1861
+ const [loadingThread, setLoadingThread] = useState(true);
1862
+ const historyControlsRef = useRef(null);
1863
+ const selectModel = useCallback((modelId) => {
1864
+ if (!models.some((model) => model.id === modelId)) return;
1865
+ onModelChange(modelId);
1866
+ }, [models, onModelChange]);
1867
+ const selectThread = useCallback(async (thread) => {
1868
+ setLoadingThread(true);
1869
+ setThreadError(null);
1870
+ try {
1871
+ const transcript = await threadClient.read(thread.id);
1872
+ setSelectedThread(transcript.thread);
1873
+ setInitialMessages(transcript.messages);
1874
+ setHistoryOpen(false);
1875
+ if (storageKey) window.localStorage.setItem(storageKey, thread.id);
1876
+ } catch (error) {
1877
+ setThreadError(parseSurveyAssistantError(error).detail ?? t("errors.fallbackDetail"));
1878
+ } finally {
1879
+ setLoadingThread(false);
1880
+ }
1881
+ }, [
1882
+ storageKey,
1883
+ t,
1884
+ threadClient
1885
+ ]);
1886
+ const createThread = useCallback(async () => {
1887
+ setLoadingThread(true);
1888
+ setThreadError(null);
1889
+ try {
1890
+ const thread = await threadClient.create();
1891
+ setThreads((current) => [thread, ...current]);
1892
+ setSelectedThread(thread);
1893
+ setInitialMessages([]);
1894
+ setHistoryOpen(false);
1895
+ if (storageKey) window.localStorage.setItem(storageKey, thread.id);
1896
+ } catch (error) {
1897
+ setThreadError(parseSurveyAssistantError(error).detail ?? t("errors.fallbackDetail"));
1898
+ } finally {
1899
+ setLoadingThread(false);
1900
+ }
1901
+ }, [
1902
+ storageKey,
1903
+ t,
1904
+ threadClient
1905
+ ]);
1906
+ useEffect(() => {
1907
+ let cancelled = false;
1908
+ threadClient.list().then(async (availableThreads) => {
1909
+ if (cancelled) return;
1910
+ setThreads(availableThreads);
1911
+ const rememberedId = storageKey ? window.localStorage.getItem(storageKey) : null;
1912
+ const initialThread = availableThreads.find((thread) => thread.id === rememberedId) ?? availableThreads[0];
1913
+ if (initialThread) await selectThread(initialThread);
1914
+ else await createThread();
1915
+ }).catch((error) => {
1916
+ if (!cancelled) {
1917
+ setThreadError(parseSurveyAssistantError(error).detail ?? t("errors.fallbackDetail"));
1918
+ setLoadingThread(false);
1919
+ }
1920
+ });
1921
+ return () => {
1922
+ cancelled = true;
1923
+ };
1924
+ }, [
1925
+ createThread,
1926
+ selectThread,
1927
+ storageKey,
1928
+ t,
1929
+ threadClient
1930
+ ]);
1931
+ const renameThread = useCallback(async (thread, title) => {
1932
+ const normalized = title.trim();
1933
+ if (!normalized || normalized === thread.title) {
1934
+ setEditingThreadId(null);
1935
+ setEditingTitle("");
1936
+ return;
1937
+ }
1938
+ try {
1939
+ const updated = await threadClient.rename(thread.id, normalized);
1940
+ setThreads((current) => current.map((item) => item.id === updated.id ? updated : item));
1941
+ setSelectedThread((current) => current?.id === updated.id ? updated : current);
1942
+ setEditingThreadId(null);
1943
+ setEditingTitle("");
1944
+ } catch (error) {
1945
+ setThreadError(parseSurveyAssistantError(error).detail ?? t("errors.fallbackDetail"));
1946
+ }
1947
+ }, [t, threadClient]);
1948
+ const setAutomaticTitle = useCallback(async (thread, title) => {
1949
+ try {
1950
+ const updated = await threadClient.setAutomaticTitle(thread.id, title);
1951
+ setThreads((current) => current.map((item) => item.id === updated.id ? updated : item));
1952
+ setSelectedThread((current) => current?.id === updated.id ? updated : current);
1953
+ setEditingThreadId(null);
1954
+ setEditingTitle("");
1955
+ } catch (error) {
1956
+ setThreadError(parseSurveyAssistantError(error).detail ?? t("errors.fallbackDetail"));
1957
+ }
1958
+ }, [t, threadClient]);
1959
+ const beginEditingThread = useCallback((thread) => {
1960
+ setEditingThreadId(thread.id);
1961
+ setEditingTitle(thread.title);
1962
+ }, []);
1963
+ const cancelEditingThread = useCallback(() => {
1964
+ setEditingThreadId(null);
1965
+ setEditingTitle("");
1966
+ }, []);
1967
+ const commitEditingThread = useCallback(async () => {
1968
+ if (!editingThreadId) return;
1969
+ const thread = threads.find((item) => item.id === editingThreadId);
1970
+ const title = editingTitle;
1971
+ if (thread) await renameThread(thread, title);
1972
+ }, [
1973
+ editingThreadId,
1974
+ editingTitle,
1975
+ renameThread,
1976
+ threads
1977
+ ]);
1978
+ const deleteThread = useCallback(async (thread) => {
1979
+ if (!(onConfirmDeleteThread ? await onConfirmDeleteThread(thread) : window.confirm(t("threads.deleteConfirm", { title: thread.title })))) return;
1980
+ try {
1981
+ await threadClient.delete(thread.id);
1982
+ const remaining = threads.filter((item) => item.id !== thread.id);
1983
+ setThreads(remaining);
1984
+ if (selectedThread?.id === thread.id) if (remaining[0]) await selectThread(remaining[0]);
1985
+ else await createThread();
1986
+ } catch (error) {
1987
+ setThreadError(parseSurveyAssistantError(error).detail ?? t("errors.fallbackDetail"));
1988
+ }
1989
+ }, [
1990
+ createThread,
1991
+ onConfirmDeleteThread,
1992
+ selectThread,
1993
+ selectedThread,
1994
+ t,
1995
+ threadClient,
1996
+ threads
1997
+ ]);
1998
+ const handleFirstSubmit = useCallback((text) => {
1999
+ if (!selectedThread || initialMessages.length > 0) return;
2000
+ setAutomaticTitle(selectedThread, createThreadTitle(text, t("threads.newChat")));
2001
+ }, [
2002
+ initialMessages.length,
2003
+ selectedThread,
2004
+ setAutomaticTitle,
2005
+ t
2006
+ ]);
2007
+ const refreshThreadList = useCallback(async (reportError) => {
2008
+ try {
2009
+ const availableThreads = await threadClient.list();
2010
+ setThreads(availableThreads);
2011
+ const current = availableThreads.find((thread) => thread.id === selectedThread?.id);
2012
+ if (current && !editingThreadId) setSelectedThread((selected) => {
2013
+ if (!selected || selected.id !== current.id) return selected;
2014
+ return selected.title === current.title && selected.titleMode === current.titleMode && selected.updatedAt === current.updatedAt ? selected : current;
2015
+ });
2016
+ } catch (error) {
2017
+ if (reportError) setThreadError(parseSurveyAssistantError(error).detail ?? t("errors.fallbackDetail"));
2018
+ }
2019
+ }, [
2020
+ editingThreadId,
2021
+ selectedThread?.id,
2022
+ t,
2023
+ threadClient
2024
+ ]);
2025
+ const toggleHistory = useCallback(() => {
2026
+ if (historyOpen) {
2027
+ commitEditingThread();
2028
+ setHistoryOpen(false);
2029
+ return;
2030
+ }
2031
+ setHistoryOpen(true);
2032
+ refreshThreadList(true);
2033
+ }, [
2034
+ commitEditingThread,
2035
+ historyOpen,
2036
+ refreshThreadList
2037
+ ]);
2038
+ const handleTurnSettled = useCallback(() => {
2039
+ refreshThreadList(false);
2040
+ }, [refreshThreadList]);
2041
+ useEffect(() => {
2042
+ if (!historyOpen) return;
2043
+ const handlePointerDown = (event) => {
2044
+ if (!historyControlsRef.current?.contains(event.target)) {
2045
+ commitEditingThread();
2046
+ setHistoryOpen(false);
2047
+ }
2048
+ };
2049
+ const handleKeyDown = (event) => {
2050
+ if (event.key === "Escape") {
2051
+ cancelEditingThread();
2052
+ setHistoryOpen(false);
2053
+ }
2054
+ };
2055
+ document.addEventListener("pointerdown", handlePointerDown);
2056
+ document.addEventListener("keydown", handleKeyDown);
2057
+ return () => {
2058
+ document.removeEventListener("pointerdown", handlePointerDown);
2059
+ document.removeEventListener("keydown", handleKeyDown);
2060
+ };
2061
+ }, [
2062
+ cancelEditingThread,
2063
+ commitEditingThread,
2064
+ historyOpen
2065
+ ]);
2066
+ useEffect(() => {
2067
+ const handleFocus = () => {
2068
+ refreshThreadList(false);
2069
+ };
2070
+ window.addEventListener("focus", handleFocus);
2071
+ return () => window.removeEventListener("focus", handleFocus);
2072
+ }, [refreshThreadList]);
2073
+ if (loadingThread || !selectedThread) return /* @__PURE__ */ jsxs("aside", {
2074
+ className: cn("flex h-full min-h-0 flex-col overflow-visible bg-background", !embedded && "rounded-xl border border-border shadow-sm"),
2075
+ children: [/* @__PURE__ */ jsx("header", {
2076
+ className: "box-content flex h-9 shrink-0 items-center border-b border-border bg-muted/20 px-2.5",
2077
+ children: /* @__PURE__ */ jsx(SurveyAssistantHeaderIdentity, {
2078
+ indicatorClassName: "animate-pulse bg-blue-500",
2079
+ status: t("panel.loadingChats"),
2080
+ title: t("panel.title")
2081
+ })
2082
+ }), /* @__PURE__ */ jsxs("div", {
2083
+ className: "flex min-h-0 flex-1 items-center justify-center text-muted-foreground text-sm",
2084
+ children: [/* @__PURE__ */ jsx(LoaderCircleIcon, { className: "mr-2 size-4 animate-spin" }), t("panel.loadingChats")]
2085
+ })]
2086
+ });
2087
+ const threadControls = /* @__PURE__ */ jsxs("div", {
2088
+ className: "relative flex shrink-0 items-center gap-1",
2089
+ ref: historyControlsRef,
2090
+ children: [
2091
+ /* @__PURE__ */ jsx("button", {
2092
+ "aria-label": t("threads.newChat"),
2093
+ className: "flex size-8 items-center justify-center rounded-lg text-muted-foreground hover:bg-muted hover:text-foreground",
2094
+ onClick: () => void createThread(),
2095
+ title: t("threads.newChat"),
2096
+ type: "button",
2097
+ children: /* @__PURE__ */ jsx(PlusIcon, { className: "size-4" })
2098
+ }),
2099
+ /* @__PURE__ */ jsx("button", {
2100
+ "aria-expanded": historyOpen,
2101
+ "aria-label": t("threads.history"),
2102
+ className: "flex size-8 items-center justify-center rounded-lg text-muted-foreground hover:bg-muted hover:text-foreground",
2103
+ onClick: toggleHistory,
2104
+ title: t("threads.history"),
2105
+ type: "button",
2106
+ children: /* @__PURE__ */ jsx(HistoryIcon, { className: "size-4" })
2107
+ }),
2108
+ historyOpen && /* @__PURE__ */ jsxs("div", {
2109
+ className: "absolute top-10 right-0 z-30 w-72 overflow-hidden rounded-lg border border-border bg-popover text-popover-foreground shadow-lg",
2110
+ children: [/* @__PURE__ */ jsx("div", {
2111
+ className: "border-b border-border px-3 py-2 font-medium text-xs",
2112
+ children: t("threads.recent")
2113
+ }), /* @__PURE__ */ jsx("div", {
2114
+ className: "max-h-72 overflow-y-auto p-1",
2115
+ children: threads.map((thread) => /* @__PURE__ */ jsxs("div", {
2116
+ className: cn("group flex min-h-10 items-center gap-1 rounded-md px-2", thread.id === selectedThread.id ? "bg-muted" : "hover:bg-muted/60"),
2117
+ children: [
2118
+ editingThreadId === thread.id ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("input", {
2119
+ autoFocus: true,
2120
+ className: "min-w-0 flex-1 rounded border border-input bg-background px-1.5 py-1 text-xs outline-none focus:ring-1 focus:ring-ring",
2121
+ onBlur: () => void commitEditingThread(),
2122
+ onChange: (event) => setEditingTitle(event.currentTarget.value),
2123
+ onKeyDown: (event) => {
2124
+ if (event.key === "Enter") {
2125
+ event.preventDefault();
2126
+ commitEditingThread();
2127
+ }
2128
+ if (event.key === "Escape") {
2129
+ event.preventDefault();
2130
+ event.stopPropagation();
2131
+ cancelEditingThread();
2132
+ setHistoryOpen(false);
2133
+ }
2134
+ },
2135
+ value: editingTitle
2136
+ }), thread.titleMode === "manual" && /* @__PURE__ */ jsxs("button", {
2137
+ "aria-label": t("threads.automaticTitle"),
2138
+ className: "flex h-7 shrink-0 items-center gap-1 rounded px-1.5 text-muted-foreground text-xs hover:bg-background hover:text-foreground",
2139
+ onClick: () => void setAutomaticTitle(thread),
2140
+ onMouseDown: (event) => event.preventDefault(),
2141
+ title: t("threads.automaticTitle"),
2142
+ type: "button",
2143
+ children: [/* @__PURE__ */ jsx(SparklesIcon, { className: "size-3" }), /* @__PURE__ */ jsx("span", { children: t("threads.automaticShort") })]
2144
+ })] }) : /* @__PURE__ */ jsx("button", {
2145
+ className: "min-w-0 flex-1 truncate py-2 text-left text-xs",
2146
+ onClick: () => void selectThread(thread),
2147
+ type: "button",
2148
+ children: thread.title
2149
+ }),
2150
+ /* @__PURE__ */ jsx("button", {
2151
+ "aria-label": t("threads.rename", { title: thread.title }),
2152
+ className: "flex size-7 shrink-0 items-center justify-center rounded text-muted-foreground opacity-0 hover:bg-background hover:text-foreground group-hover:opacity-100",
2153
+ onClick: () => beginEditingThread(thread),
2154
+ title: t("threads.renameTitle"),
2155
+ type: "button",
2156
+ children: /* @__PURE__ */ jsx(PencilIcon, { className: "size-3.5" })
2157
+ }),
2158
+ /* @__PURE__ */ jsx("button", {
2159
+ "aria-label": t("threads.delete", { title: thread.title }),
2160
+ className: "flex size-7 shrink-0 items-center justify-center rounded text-muted-foreground opacity-0 hover:bg-background hover:text-foreground group-hover:opacity-100",
2161
+ onClick: () => void deleteThread(thread),
2162
+ title: t("threads.deleteTitle"),
2163
+ type: "button",
2164
+ children: /* @__PURE__ */ jsx(Trash2Icon, { className: "size-3.5" })
2165
+ })
2166
+ ]
2167
+ }, thread.id))
2168
+ })]
2169
+ })
2170
+ ]
2171
+ });
2172
+ return /* @__PURE__ */ jsxs("div", {
2173
+ className: "relative h-full min-h-0",
2174
+ children: [/* @__PURE__ */ createElement(SurveyAssistantChatPanel, {
2175
+ ...sessionProps,
2176
+ embedded,
2177
+ initialMessages,
2178
+ key: selectedThread.id,
2179
+ modelId,
2180
+ models,
2181
+ onModelChange: selectModel,
2182
+ onFirstSubmit: handleFirstSubmit,
2183
+ onTurnSettled: handleTurnSettled,
2184
+ resourceId,
2185
+ threadControls,
2186
+ threadId: selectedThread.id,
2187
+ threadTitle: selectedThread.title
2188
+ }), threadError && /* @__PURE__ */ jsxs("div", {
2189
+ className: "absolute right-3 bottom-3 left-3 z-40 rounded-lg border border-destructive/20 bg-background px-3 py-2 text-xs shadow-lg",
2190
+ children: [
2191
+ /* @__PURE__ */ jsx("div", {
2192
+ className: "font-medium",
2193
+ children: t("panel.controlsUnavailable")
2194
+ }),
2195
+ /* @__PURE__ */ jsx("div", {
2196
+ className: "mt-0.5 text-muted-foreground",
2197
+ children: threadError
2198
+ }),
2199
+ /* @__PURE__ */ jsx("button", {
2200
+ className: "mt-1 font-medium hover:underline",
2201
+ onClick: () => setThreadError(null),
2202
+ type: "button",
2203
+ children: t("errors.dismiss")
2204
+ })
2205
+ ]
2206
+ })]
2207
+ });
2208
+ };
2209
+ const SurveyAssistantPanel = ({ language, localizations, ...props }) => /* @__PURE__ */ jsx("div", {
2210
+ className: "case-survey-assistant",
2211
+ children: /* @__PURE__ */ jsx(SurveyAssistantLocalizationProvider, {
2212
+ language,
2213
+ localizations,
2214
+ children: /* @__PURE__ */ jsx(SurveyAssistantPanelContent, { ...props })
2215
+ })
2216
+ });
2217
+ //#endregion
2218
+ //#region src/ui/connected-survey-assistant-panel.tsx
2219
+ const ConnectedSurveyAssistantPanel = ({ modelCatalogApi, modelStorageKey, onModelCatalogError, onModelChange, ...props }) => {
2220
+ const catalog = useSurveyAssistantModelCatalog({
2221
+ api: modelCatalogApi,
2222
+ onModelChange,
2223
+ storageKey: modelStorageKey
2224
+ });
2225
+ useEffect(() => {
2226
+ if (catalog.error) onModelCatalogError?.(catalog.error);
2227
+ }, [catalog.error, onModelCatalogError]);
2228
+ return /* @__PURE__ */ jsx(SurveyAssistantPanel, {
2229
+ ...props,
2230
+ modelId: catalog.modelId,
2231
+ models: catalog.models,
2232
+ onModelChange: catalog.selectModel
2233
+ });
2234
+ };
2235
+ //#endregion
2236
+ export { ConnectedSurveyAssistantPanel, SurveyAssistantLocalizationProvider, SurveyAssistantPanel, defaultSurveyAssistantLocalizations, surveyAssistantEnMessages, surveyAssistantNlMessages, useSurveyAssistantTranslation };
2237
+
2238
+ //# sourceMappingURL=ui.mjs.map