@case-framework/survey-assistant 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +12 -4
  2. package/dist/capabilities-default.d.mts +1 -1
  3. package/dist/capabilities-default.mjs +1 -1
  4. package/dist/{constants-DrGZXtQf.d.mts → constants-k1b_evIb.d.mts} +5 -2
  5. package/dist/constants-k1b_evIb.d.mts.map +1 -0
  6. package/dist/{controller-proxy-CpkQRezb.mjs → controller-proxy-jWIPOZAv.mjs} +17 -8
  7. package/dist/controller-proxy-jWIPOZAv.mjs.map +1 -0
  8. package/dist/{controller-proxy-DmBdzqKs.d.mts → controller-proxy-quQjtA7j.d.mts} +2 -2
  9. package/dist/{controller-proxy-DmBdzqKs.d.mts.map → controller-proxy-quQjtA7j.d.mts.map} +1 -1
  10. package/dist/{engine-CWTuN3o2.mjs → engine-BvfUx_Jx.mjs} +85 -12
  11. package/dist/engine-BvfUx_Jx.mjs.map +1 -0
  12. package/dist/engine.d.mts +2 -2
  13. package/dist/engine.mjs +3 -3
  14. package/dist/{form-authoring-DCd9XfXf.mjs → form-authoring-BQh40yDb.mjs} +2 -2
  15. package/dist/{form-authoring-DCd9XfXf.mjs.map → form-authoring-BQh40yDb.mjs.map} +1 -1
  16. package/dist/{index-D4OitUg4.d.mts → index-8x1Eqlnz.d.mts} +4 -1
  17. package/dist/index-8x1Eqlnz.d.mts.map +1 -0
  18. package/dist/{index-Dxgg0-D_.d.mts → index-BDp6S7r9.d.mts} +11 -5
  19. package/dist/index-BDp6S7r9.d.mts.map +1 -0
  20. package/dist/{index-DZk20e3k.d.mts → index-biZnRl-h.d.mts} +3 -3
  21. package/dist/{index-DZk20e3k.d.mts.map → index-biZnRl-h.d.mts.map} +1 -1
  22. package/dist/{index-Bo4dHAo8.d.mts → index-r8HVcn5b.d.mts} +8 -4
  23. package/dist/index-r8HVcn5b.d.mts.map +1 -0
  24. package/dist/{memory-thread-repository-acbz6HZJ.mjs → memory-thread-repository-xBZOog_q.mjs} +49 -21
  25. package/dist/memory-thread-repository-xBZOog_q.mjs.map +1 -0
  26. package/dist/{protocol-DATvkr1S.mjs → protocol-DJUP4gc0.mjs} +10 -4
  27. package/dist/protocol-DJUP4gc0.mjs.map +1 -0
  28. package/dist/protocol.d.mts +1 -1
  29. package/dist/protocol.mjs +1 -1
  30. package/dist/{react-Br5YE8H-.mjs → react-CY6N7lVi.mjs} +25 -5
  31. package/dist/react-CY6N7lVi.mjs.map +1 -0
  32. package/dist/react-integration.d.mts +1 -1
  33. package/dist/react-integration.mjs +1 -1
  34. package/dist/react.d.mts +2 -2
  35. package/dist/react.mjs +2 -2
  36. package/dist/{request-context-BEjnaaFr.mjs → request-context-DixY5DYu.mjs} +3 -3
  37. package/dist/request-context-DixY5DYu.mjs.map +1 -0
  38. package/dist/server-agent.d.mts +4 -4
  39. package/dist/server-agent.d.mts.map +1 -1
  40. package/dist/server-agent.mjs +4 -3
  41. package/dist/server-agent.mjs.map +1 -1
  42. package/dist/server-runtime.d.mts +340 -7
  43. package/dist/server-runtime.d.mts.map +1 -1
  44. package/dist/server-runtime.mjs +321 -23
  45. package/dist/server-runtime.mjs.map +1 -1
  46. package/dist/server-tasks.d.mts +1 -1
  47. package/dist/server-tasks.mjs +5 -4
  48. package/dist/server-tasks.mjs.map +1 -1
  49. package/dist/server-tools.d.mts +1 -1
  50. package/dist/server-tools.mjs +1 -1
  51. package/dist/server.d.mts +6 -6
  52. package/dist/server.d.mts.map +1 -1
  53. package/dist/server.mjs +2 -2
  54. package/dist/{tasks-DQgL449A.d.mts → tasks-BWmTMXoK.d.mts} +3 -3
  55. package/dist/{tasks-DQgL449A.d.mts.map → tasks-BWmTMXoK.d.mts.map} +1 -1
  56. package/dist/{thread-handlers-CeYl45xG.d.mts → thread-handlers-D8gkLfU6.d.mts} +13 -5
  57. package/dist/{thread-handlers-CeYl45xG.d.mts.map → thread-handlers-D8gkLfU6.d.mts.map} +1 -1
  58. package/dist/{tools-Dg9HZrYU.mjs → tools-TXI8bfzZ.mjs} +25 -6
  59. package/dist/{tools-Dg9HZrYU.mjs.map → tools-TXI8bfzZ.mjs.map} +1 -1
  60. package/dist/ui.css +1 -1
  61. package/dist/ui.d.mts +3 -3
  62. package/dist/ui.d.mts.map +1 -1
  63. package/dist/ui.mjs +370 -127
  64. package/dist/ui.mjs.map +1 -1
  65. package/docs/integration.md +83 -62
  66. package/package.json +3 -3
  67. package/dist/constants-DrGZXtQf.d.mts.map +0 -1
  68. package/dist/controller-proxy-CpkQRezb.mjs.map +0 -1
  69. package/dist/engine-CWTuN3o2.mjs.map +0 -1
  70. package/dist/index-Bo4dHAo8.d.mts.map +0 -1
  71. package/dist/index-D4OitUg4.d.mts.map +0 -1
  72. package/dist/index-Dxgg0-D_.d.mts.map +0 -1
  73. package/dist/memory-thread-repository-acbz6HZJ.mjs.map +0 -1
  74. package/dist/protocol-DATvkr1S.mjs.map +0 -1
  75. package/dist/react-Br5YE8H-.mjs.map +0 -1
  76. package/dist/request-context-BEjnaaFr.mjs.map +0 -1
package/dist/ui.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
- import { jt as parseSurveyAssistantError, l as surveyAssistantTurnFinalSchema, n as SURVEY_ASSISTANT_TURN_FINAL_PART_TYPE } from "./protocol-DATvkr1S.mjs";
3
- import { f as unwrapSurveyAssistantToolOutput, i as createSurveyAssistantThreadClient, l as parseSurveyChangeSet, p as isSurveyAssistantTurnActive, r as useSurveyAssistantModelCatalog, t as useSurveyAssistantSession } from "./react-Br5YE8H-.mjs";
4
- import { o as connectSurveyAssistantThreadLauncher } from "./controller-proxy-CpkQRezb.mjs";
2
+ import { jt as parseSurveyAssistantError, l as surveyAssistantTurnFinalSchema, n as SURVEY_ASSISTANT_TURN_FINAL_PART_TYPE } from "./protocol-DJUP4gc0.mjs";
3
+ import { f as unwrapSurveyAssistantToolOutput, i as createSurveyAssistantThreadClient, l as parseSurveyChangeSet, p as isSurveyAssistantTurnActive, r as useSurveyAssistantModelCatalog, t as useSurveyAssistantSession } from "./react-CY6N7lVi.mjs";
4
+ import { o as connectSurveyAssistantThreadLauncher } from "./controller-proxy-jWIPOZAv.mjs";
5
5
  import { getToolName, isFileUIPart, isReasoningUIPart, isToolUIPart } from "ai";
6
6
  import { createContext, createElement, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
7
7
  import { Collapsible, Select, Slot } from "radix-ui";
@@ -9,7 +9,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
9
  import { clsx } from "clsx";
10
10
  import { twMerge } from "tailwind-merge";
11
11
  import { useControllableState } from "@radix-ui/react-use-controllable-state";
12
- import { ArrowDownIcon, ArrowUpIcon, BotIcon, BrainIcon, CheckCircleIcon, CheckIcon, ChevronDownIcon, ChevronRightIcon, ChevronUpIcon, ClockIcon, DotIcon, FileIcon, HistoryIcon, ListChecksIcon, LoaderCircleIcon, MessageSquarePlusIcon, PaperclipIcon, PencilIcon, PlusIcon, SparklesIcon, SquareIcon, Trash2Icon, WandSparklesIcon, WrenchIcon, XCircleIcon, XIcon } from "lucide-react";
12
+ import { ArrowDownIcon, ArrowUpIcon, BotIcon, BrainIcon, CheckCircleIcon, CheckIcon, ChevronDownIcon, ChevronRightIcon, ChevronUpIcon, ClockIcon, DotIcon, FileIcon, HistoryIcon, ListChecksIcon, LoaderCircleIcon, MessageSquarePlusIcon, PaperclipIcon, PencilIcon, PlusIcon, SearchIcon, SparklesIcon, SquareIcon, Trash2Icon, WandSparklesIcon, WrenchIcon, XCircleIcon, XIcon } from "lucide-react";
13
13
  import { motion } from "motion/react";
14
14
  import { createLocalizationTranslator, mergeLocalizationCatalogs } from "@case-framework/ui-localization";
15
15
  import { cva } from "class-variance-authority";
@@ -80,13 +80,30 @@ const surveyAssistantEnMessages = {
80
80
  },
81
81
  empty: {
82
82
  title: "What are you working on?",
83
- description: "Draft, inspect, or refine your survey - just describe what you need, or start with one of these."
83
+ description: "Draft, inspect, or refine your survey - just describe what you need, or start with one of these.",
84
+ newSurveyTitle: "Create with the assistant",
85
+ newSurveyDescription: "Describe the survey you need, share a brief, or plan the right questions together."
84
86
  },
85
87
  quickActions: {
88
+ draftSurvey: {
89
+ label: "Draft a survey",
90
+ description: "Create a first draft from your goals",
91
+ prompt: "Help me create a first draft of a new survey. Ask me about its topic, audience, and goals before drafting it."
92
+ },
93
+ useBrief: {
94
+ label: "Use a brief or notes",
95
+ description: "Turn requirements into a survey",
96
+ prompt: "Help me turn a brief or set of notes into a survey. Ask me to paste the text or attach the document."
97
+ },
98
+ planSurvey: {
99
+ label: "Plan the questions",
100
+ description: "Outline the structure before drafting",
101
+ prompt: "Help me plan the questions and structure for a new survey before we draft it."
102
+ },
86
103
  review: {
87
104
  label: "Review survey",
88
105
  description: "Spot issues and suggest improvements",
89
- prompt: "Review the current survey and suggest improvements."
106
+ prompt: "Run a deterministic audit of the current survey, including expression-reference integrity, then review its quality and suggest improvements."
90
107
  },
91
108
  create: {
92
109
  label: "New question",
@@ -97,6 +114,11 @@ const surveyAssistantEnMessages = {
97
114
  label: "Refine wording",
98
115
  description: "Polish respondent-facing text",
99
116
  prompt: "Suggest improvements to the respondent-facing wording."
117
+ },
118
+ refineSelection: {
119
+ label: "Refine selected item",
120
+ description: "Polish the wording in context",
121
+ prompt: "Suggest improvements to the respondent-facing wording of the selected item."
100
122
  }
101
123
  },
102
124
  input: {
@@ -111,6 +133,8 @@ const surveyAssistantEnMessages = {
111
133
  defaultModel: "Default model"
112
134
  },
113
135
  conversation: {
136
+ loadEarlier: "Load earlier messages",
137
+ loadingEarlier: "Loading earlier messages…",
114
138
  noMessages: "No messages yet",
115
139
  startConversation: "Start a conversation to see messages here",
116
140
  scrollToLatest: "Scroll to latest message",
@@ -246,6 +270,11 @@ const surveyAssistantEnMessages = {
246
270
  newChat: "New chat",
247
271
  history: "Chat history",
248
272
  recent: "Recent chats",
273
+ search: "Search chats",
274
+ searching: "Searching older chats…",
275
+ noMatches: "No matching chats",
276
+ loadOlder: "Load older chats",
277
+ loadingOlder: "Loading older chats…",
249
278
  automaticTitle: "Use automatic title",
250
279
  automaticShort: "Auto",
251
280
  rename: "Rename {title}",
@@ -266,13 +295,30 @@ const surveyAssistantNlMessages = {
266
295
  },
267
296
  empty: {
268
297
  title: "Waar werk je aan?",
269
- description: "Maak, controleer of verbeter je survey. Beschrijf wat je nodig hebt of kies een van deze opties."
298
+ description: "Maak, controleer of verbeter je survey. Beschrijf wat je nodig hebt of kies een van deze opties.",
299
+ newSurveyTitle: "Maak iets met de assistent",
300
+ newSurveyDescription: "Beschrijf de survey die je nodig hebt, deel een briefing of plan samen de juiste vragen."
270
301
  },
271
302
  quickActions: {
303
+ draftSurvey: {
304
+ label: "Een survey opstellen",
305
+ description: "Maak een eerste versie vanuit je doelen",
306
+ prompt: "Help me een eerste versie van een nieuwe survey te maken. Vraag me vóór het opstellen naar het onderwerp, de doelgroep en de doelen."
307
+ },
308
+ useBrief: {
309
+ label: "Gebruik een briefing of notities",
310
+ description: "Zet vereisten om in een survey",
311
+ prompt: "Help me een briefing of verzameling notities om te zetten in een survey. Vraag me de tekst te plakken of het document toe te voegen."
312
+ },
313
+ planSurvey: {
314
+ label: "Plan de vragen",
315
+ description: "Schets eerst de structuur",
316
+ prompt: "Help me de vragen en structuur voor een nieuwe survey te plannen voordat we die opstellen."
317
+ },
272
318
  review: {
273
319
  label: "Survey controleren",
274
320
  description: "Problemen vinden en verbeteringen voorstellen",
275
- prompt: "Controleer de huidige survey en stel verbeteringen voor."
321
+ prompt: "Voer een deterministische controle van de huidige survey uit, inclusief de geldigheid van expressieverwijzingen, beoordeel daarna de kwaliteit en stel verbeteringen voor."
276
322
  },
277
323
  create: {
278
324
  label: "Nieuwe vraag",
@@ -283,6 +329,11 @@ const surveyAssistantNlMessages = {
283
329
  label: "Formulering verbeteren",
284
330
  description: "Tekst voor respondenten aanscherpen",
285
331
  prompt: "Stel verbeteringen voor voor de tekst die respondenten zien."
332
+ },
333
+ refineSelection: {
334
+ label: "Geselecteerd item verbeteren",
335
+ description: "Scherp de formulering in context aan",
336
+ prompt: "Stel verbeteringen voor voor de respondentgerichte formulering van het geselecteerde item."
286
337
  }
287
338
  },
288
339
  input: {
@@ -297,6 +348,8 @@ const surveyAssistantNlMessages = {
297
348
  defaultModel: "Standaardmodel"
298
349
  },
299
350
  conversation: {
351
+ loadEarlier: "Eerdere berichten laden",
352
+ loadingEarlier: "Eerdere berichten laden…",
300
353
  noMessages: "Nog geen berichten",
301
354
  startConversation: "Start een gesprek om hier berichten te zien",
302
355
  scrollToLatest: "Naar het nieuwste bericht scrollen",
@@ -432,6 +485,11 @@ const surveyAssistantNlMessages = {
432
485
  newChat: "Nieuwe chat",
433
486
  history: "Chatgeschiedenis",
434
487
  recent: "Recente chats",
488
+ search: "Chats zoeken",
489
+ searching: "Oudere chats zoeken…",
490
+ noMatches: "Geen overeenkomende chats",
491
+ loadOlder: "Oudere chats laden",
492
+ loadingOlder: "Oudere chats laden…",
435
493
  automaticTitle: "Automatische titel gebruiken",
436
494
  automaticShort: "Automatisch",
437
495
  rename: "{title} hernoemen",
@@ -1351,26 +1409,61 @@ const getPersistedMessageTurnStatus = (message) => {
1351
1409
  };
1352
1410
  //#endregion
1353
1411
  //#region src/ui/survey-assistant-panel.tsx
1354
- const getAssistantQuickPrompts = (t) => [
1355
- {
1356
- label: t("quickActions.review.label"),
1357
- description: t("quickActions.review.description"),
1358
- icon: ListChecksIcon,
1359
- prompt: t("quickActions.review.prompt")
1360
- },
1361
- {
1362
- label: t("quickActions.create.label"),
1363
- description: t("quickActions.create.description"),
1364
- icon: MessageSquarePlusIcon,
1365
- prompt: t("quickActions.create.prompt")
1366
- },
1367
- {
1368
- label: t("quickActions.refine.label"),
1369
- description: t("quickActions.refine.description"),
1370
- icon: WandSparklesIcon,
1371
- prompt: t("quickActions.refine.prompt")
1412
+ const isRootSurveyItem = (item) => typeof item.config === "object" && item.config !== null && item.config.isRoot === true;
1413
+ const getAssistantEmptyStateContext = (controller) => {
1414
+ try {
1415
+ const snapshot = controller?.getSnapshot();
1416
+ if (!snapshot) return "survey";
1417
+ const surveyItems = snapshot.survey.surveyItems.filter((item) => !isRootSurveyItem(item));
1418
+ if (surveyItems.length === 0) return "new-survey";
1419
+ if (surveyItems.some((item) => item.id === snapshot.ui.selectedItemId)) return "selected-item";
1420
+ return "survey";
1421
+ } catch {
1422
+ return "survey";
1372
1423
  }
1373
- ];
1424
+ };
1425
+ const getAssistantQuickPrompts = (t, context) => {
1426
+ if (context === "new-survey") return [
1427
+ {
1428
+ label: t("quickActions.draftSurvey.label"),
1429
+ description: t("quickActions.draftSurvey.description"),
1430
+ icon: WandSparklesIcon,
1431
+ prompt: t("quickActions.draftSurvey.prompt")
1432
+ },
1433
+ {
1434
+ label: t("quickActions.useBrief.label"),
1435
+ description: t("quickActions.useBrief.description"),
1436
+ icon: FileIcon,
1437
+ prompt: t("quickActions.useBrief.prompt")
1438
+ },
1439
+ {
1440
+ label: t("quickActions.planSurvey.label"),
1441
+ description: t("quickActions.planSurvey.description"),
1442
+ icon: ListChecksIcon,
1443
+ prompt: t("quickActions.planSurvey.prompt")
1444
+ }
1445
+ ];
1446
+ return [
1447
+ {
1448
+ label: t("quickActions.review.label"),
1449
+ description: t("quickActions.review.description"),
1450
+ icon: ListChecksIcon,
1451
+ prompt: t("quickActions.review.prompt")
1452
+ },
1453
+ {
1454
+ label: t("quickActions.create.label"),
1455
+ description: t("quickActions.create.description"),
1456
+ icon: MessageSquarePlusIcon,
1457
+ prompt: t("quickActions.create.prompt")
1458
+ },
1459
+ {
1460
+ label: t(context === "selected-item" ? "quickActions.refineSelection.label" : "quickActions.refine.label"),
1461
+ description: t(context === "selected-item" ? "quickActions.refineSelection.description" : "quickActions.refine.description"),
1462
+ icon: WandSparklesIcon,
1463
+ prompt: t(context === "selected-item" ? "quickActions.refineSelection.prompt" : "quickActions.refine.prompt")
1464
+ }
1465
+ ];
1466
+ };
1374
1467
  const isSurveyAssistantAttachmentReferencePart = (part) => {
1375
1468
  if (part.type !== "data-survey-assistant-attachment" || typeof part.data !== "object" || part.data === null) return false;
1376
1469
  const data = part.data;
@@ -1399,6 +1492,7 @@ const getAssistantErrorPresentation = (error, t) => {
1399
1492
  title: t(`errors.${parsed.code}.title`),
1400
1493
  description: parsed.detail ?? t("errors.fallbackDetail"),
1401
1494
  hint: t(`errors.${parsed.code}.hint`),
1495
+ retryable: parsed.retryable ?? (parsed.code === "rate_limited" || parsed.code === "request_failed"),
1402
1496
  tone: parsed.code === "model_unavailable" || parsed.code === "rate_limited" ? "warning" : "error"
1403
1497
  };
1404
1498
  };
@@ -1808,10 +1902,11 @@ const SurveyAssistantHeaderIdentity = ({ indicatorClassName, status, title }) =>
1808
1902
  })]
1809
1903
  })]
1810
1904
  });
1811
- const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = false, initialLaunch, initialMessages, modelId, models, onInitialLaunchHandled, onModelChange, onFirstSubmit, onTurnSettled, onApplyReceipts, onSettleApply, onReviewChangeSets, onTurnActiveChange, resourceId, surveyId, threadControls, threadId, threadTitle }) => {
1905
+ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = false, hasEarlierMessages, initialLaunch, initialMessages, modelId, models, onInitialLaunchHandled, onModelChange, onFirstSubmit, onLoadEarlierMessages, onTurnSettled, onApplyReceipts, onSettleApply, onReviewChangeSets, onTurnActiveChange, resourceId, surveyId, threadControls, threadId, threadTitle }) => {
1812
1906
  const t = useSurveyAssistantTranslation();
1813
- const assistantQuickPrompts = getAssistantQuickPrompts(t);
1814
- const { applyResultsByChangeId, authorizationError, dismissError, error, isStreaming, latestTurnOutcome, messages, state, status, stop, submit, retry } = useSurveyAssistantSession({
1907
+ const emptyStateContext = getAssistantEmptyStateContext(controller);
1908
+ const assistantQuickPrompts = getAssistantQuickPrompts(t, emptyStateContext);
1909
+ const { applyResultsByChangeId, authorizationError, dismissError, error, isStreaming, latestTurnOutcome, messages, prependMessages, state, status, stop, submit, retry } = useSurveyAssistantSession({
1815
1910
  api,
1816
1911
  applyPolicy,
1817
1912
  controller,
@@ -1836,6 +1931,20 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1836
1931
  const promptTextareaRef = useRef(null);
1837
1932
  const shouldRestorePromptFocusRef = useRef(false);
1838
1933
  const turnWasActiveRef = useRef(false);
1934
+ const [loadingEarlierMessages, setLoadingEarlierMessages] = useState(false);
1935
+ const loadEarlierMessages = useCallback(async () => {
1936
+ if (loadingEarlierMessages) return;
1937
+ setLoadingEarlierMessages(true);
1938
+ try {
1939
+ prependMessages(await onLoadEarlierMessages());
1940
+ } finally {
1941
+ setLoadingEarlierMessages(false);
1942
+ }
1943
+ }, [
1944
+ loadingEarlierMessages,
1945
+ onLoadEarlierMessages,
1946
+ prependMessages
1947
+ ]);
1839
1948
  const isAssistantTurnSettled = !isStreaming && (state === "completed" || state === "failed" || state === "aborted" || state === "applied");
1840
1949
  useEffect(() => {
1841
1950
  if (!isAssistantTurnActive) {
@@ -1943,16 +2052,16 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1943
2052
  className: "mx-auto w-full max-w-sm",
1944
2053
  children: [
1945
2054
  /* @__PURE__ */ jsx("div", {
1946
- className: "mb-3 flex size-10 items-center justify-center rounded-xl bg-foreground text-background shadow-sm",
1947
- children: /* @__PURE__ */ jsx(SparklesIcon, { className: "size-5" })
2055
+ className: "mb-3 flex size-9 items-center justify-center rounded-xl border border-border bg-muted text-foreground shadow-xs",
2056
+ children: /* @__PURE__ */ jsx(SparklesIcon, { className: "size-4" })
1948
2057
  }),
1949
2058
  /* @__PURE__ */ jsx("h3", {
1950
2059
  className: "font-semibold text-foreground text-base tracking-tight",
1951
- children: t("empty.title")
2060
+ children: t(emptyStateContext === "new-survey" ? "empty.newSurveyTitle" : "empty.title")
1952
2061
  }),
1953
2062
  /* @__PURE__ */ jsx("p", {
1954
2063
  className: "mt-1 text-muted-foreground text-xs leading-relaxed",
1955
- children: t("empty.description")
2064
+ children: t(emptyStateContext === "new-survey" ? "empty.newSurveyDescription" : "empty.description")
1956
2065
  }),
1957
2066
  /* @__PURE__ */ jsx("div", {
1958
2067
  className: "mt-4 flex flex-col gap-1.5",
@@ -1981,40 +2090,50 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1981
2090
  })
1982
2091
  ]
1983
2092
  })
1984
- }) : /* @__PURE__ */ jsxs(Fragment, { children: [messages.map((message, index) => {
1985
- const isLastMessage = index === messages.length - 1;
1986
- const persistedTurnStatus = getPersistedMessageTurnStatus(message);
1987
- const terminalTurnStatus = persistedTurnStatus === "aborted" || persistedTurnStatus === "failed" ? persistedTurnStatus : isLastMessage && (state === "aborted" || state === "failed") ? state : void 0;
1988
- const shouldShowProgress = isAssistantTurnActive && isLastMessage && message.role === "assistant";
1989
- return /* @__PURE__ */ jsx(Message, {
2093
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2094
+ hasEarlierMessages && /* @__PURE__ */ jsxs("button", {
2095
+ className: "mx-auto flex h-8 items-center justify-center rounded-md px-3 text-muted-foreground text-xs hover:bg-muted hover:text-foreground disabled:pointer-events-none",
2096
+ disabled: loadingEarlierMessages || isAssistantTurnActive,
2097
+ onClick: () => void loadEarlierMessages(),
2098
+ type: "button",
2099
+ children: [loadingEarlierMessages && /* @__PURE__ */ jsx(LoaderCircleIcon, { className: "mr-1.5 size-3.5 animate-spin" }), loadingEarlierMessages ? t("conversation.loadingEarlier") : t("conversation.loadEarlier")]
2100
+ }),
2101
+ messages.map((message, index) => {
2102
+ const isLastMessage = index === messages.length - 1;
2103
+ const persistedTurnStatus = getPersistedMessageTurnStatus(message);
2104
+ const terminalTurnStatus = persistedTurnStatus === "aborted" || persistedTurnStatus === "failed" ? persistedTurnStatus : isLastMessage && (state === "aborted" || state === "failed") ? state : void 0;
2105
+ const shouldShowProgress = isAssistantTurnActive && isLastMessage && message.role === "assistant";
2106
+ return /* @__PURE__ */ jsx(Message, {
2107
+ className: "max-w-[96%] gap-1",
2108
+ from: message.role,
2109
+ children: /* @__PURE__ */ jsx(MessageContent, {
2110
+ className: cn(message.role === "assistant" && "w-full space-y-2"),
2111
+ children: hasRenderableParts(message) ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(SurveyAssistantMessageParts, {
2112
+ applyResultsByChangeId,
2113
+ isLastMessage,
2114
+ isStreaming: isAssistantTurnActive,
2115
+ message,
2116
+ showProgress: shouldShowProgress,
2117
+ terminalTurnStatus
2118
+ }), isLastMessage && latestTurnOutcome && /* @__PURE__ */ jsx(SurveyAssistantTurnOutcomeNotice, { ...latestTurnOutcome })] }) : shouldShowProgress ? /* @__PURE__ */ jsx(SurveyAssistantProgressIndicator, { message }) : isLastMessage && latestTurnOutcome ? /* @__PURE__ */ jsx(SurveyAssistantTurnOutcomeNotice, { ...latestTurnOutcome }) : state === "aborted" ? /* @__PURE__ */ jsx("span", {
2119
+ className: "text-muted-foreground text-sm",
2120
+ children: t("conversation.stopped")
2121
+ }) : /* @__PURE__ */ jsx("span", {
2122
+ className: "text-muted-foreground text-sm",
2123
+ children: t("conversation.incomplete")
2124
+ })
2125
+ })
2126
+ }, message.id);
2127
+ }),
2128
+ showPendingAssistantRow && /* @__PURE__ */ jsx(Message, {
1990
2129
  className: "max-w-[96%] gap-1",
1991
- from: message.role,
2130
+ from: "assistant",
1992
2131
  children: /* @__PURE__ */ jsx(MessageContent, {
1993
- className: cn(message.role === "assistant" && "w-full space-y-2"),
1994
- children: hasRenderableParts(message) ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(SurveyAssistantMessageParts, {
1995
- applyResultsByChangeId,
1996
- isLastMessage,
1997
- isStreaming: isAssistantTurnActive,
1998
- message,
1999
- showProgress: shouldShowProgress,
2000
- terminalTurnStatus
2001
- }), isLastMessage && latestTurnOutcome && /* @__PURE__ */ jsx(SurveyAssistantTurnOutcomeNotice, { ...latestTurnOutcome })] }) : shouldShowProgress ? /* @__PURE__ */ jsx(SurveyAssistantProgressIndicator, { message }) : isLastMessage && latestTurnOutcome ? /* @__PURE__ */ jsx(SurveyAssistantTurnOutcomeNotice, { ...latestTurnOutcome }) : state === "aborted" ? /* @__PURE__ */ jsx("span", {
2002
- className: "text-muted-foreground text-sm",
2003
- children: t("conversation.stopped")
2004
- }) : /* @__PURE__ */ jsx("span", {
2005
- className: "text-muted-foreground text-sm",
2006
- children: t("conversation.incomplete")
2007
- })
2132
+ className: "w-full",
2133
+ children: /* @__PURE__ */ jsx(SurveyAssistantProgressIndicator, {})
2008
2134
  })
2009
- }, message.id);
2010
- }), showPendingAssistantRow && /* @__PURE__ */ jsx(Message, {
2011
- className: "max-w-[96%] gap-1",
2012
- from: "assistant",
2013
- children: /* @__PURE__ */ jsx(MessageContent, {
2014
- className: "w-full",
2015
- children: /* @__PURE__ */ jsx(SurveyAssistantProgressIndicator, {})
2016
2135
  })
2017
- })] })
2136
+ ] })
2018
2137
  }), /* @__PURE__ */ jsx(ConversationScrollButton, { className: "bottom-2.5" })]
2019
2138
  }),
2020
2139
  showAssistantError && /* @__PURE__ */ jsxs("div", {
@@ -2037,7 +2156,7 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
2037
2156
  className: "mt-1 text-muted-foreground/80",
2038
2157
  children: assistantErrorPresentation.hint
2039
2158
  }),
2040
- /* @__PURE__ */ jsx("button", {
2159
+ assistantErrorPresentation.retryable && /* @__PURE__ */ jsx("button", {
2041
2160
  className: "mt-1.5 font-medium text-foreground text-xs hover:underline",
2042
2161
  onClick: retry,
2043
2162
  type: "button",
@@ -2081,14 +2200,32 @@ const createThreadTitle = (text, fallback) => {
2081
2200
  const words = normalized.split(" ").slice(0, 8).join(" ");
2082
2201
  return words.length > 64 ? `${words.slice(0, 61).trimEnd()}...` : words;
2083
2202
  };
2203
+ const mergeThreads = (current, incoming) => {
2204
+ const byId = new Map(current.map((thread) => [thread.id, thread]));
2205
+ for (const thread of incoming) byId.set(thread.id, thread);
2206
+ return [...byId.values()].sort((left, right) => Date.parse(right.updatedAt) - Date.parse(left.updatedAt));
2207
+ };
2208
+ const mergeRecentThreadPage = (current, recent) => {
2209
+ if (recent.length === 0) return [];
2210
+ const oldestRecentUpdate = Math.min(...recent.map((thread) => Date.parse(thread.updatedAt)));
2211
+ return mergeThreads(Number.isFinite(oldestRecentUpdate) ? current.filter((thread) => Date.parse(thread.updatedAt) <= oldestRecentUpdate) : current, recent);
2212
+ };
2213
+ const listThreadPage = async (client, cursor) => {
2214
+ if (client.listPage) return client.listPage(cursor ? { cursor } : void 0);
2215
+ return cursor ? { threads: [] } : { threads: await client.list() };
2216
+ };
2084
2217
  const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], onConfirmDeleteThread, onModelChange = () => {}, onTurnActiveChange: onAssistantTurnActiveChange, resourceId, selectedThreadStorageKey, surveyId, threadApi, threadClient: providedThreadClient, threadLauncher, ...sessionProps }) => {
2085
2218
  const t = useSurveyAssistantTranslation();
2086
2219
  const threadClient = useMemo(() => providedThreadClient ?? createSurveyAssistantThreadClient(threadApi), [providedThreadClient, threadApi]);
2087
2220
  const storageKey = selectedThreadStorageKey === false ? null : selectedThreadStorageKey ?? `${SELECTED_THREAD_STORAGE_KEY}:${encodeURIComponent(resourceId)}${surveyId ? `:${encodeURIComponent(surveyId)}` : ""}`;
2088
2221
  const [threads, setThreads] = useState([]);
2222
+ const [nextThreadCursor, setNextThreadCursor] = useState();
2089
2223
  const [selectedThread, setSelectedThread] = useState(null);
2090
2224
  const [initialMessages, setInitialMessages] = useState([]);
2225
+ const [nextMessageCursor, setNextMessageCursor] = useState();
2091
2226
  const [historyOpen, setHistoryOpen] = useState(false);
2227
+ const [threadSearch, setThreadSearch] = useState("");
2228
+ const [loadingOlderThreads, setLoadingOlderThreads] = useState(false);
2092
2229
  const [editingThreadId, setEditingThreadId] = useState(null);
2093
2230
  const [editingTitle, setEditingTitle] = useState("");
2094
2231
  const [threadError, setThreadError] = useState(null);
@@ -2096,21 +2233,25 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2096
2233
  const [assistantTurnActive, setAssistantTurnActive] = useState(false);
2097
2234
  const [initialLaunch, setInitialLaunch] = useState();
2098
2235
  const historyControlsRef = useRef(null);
2236
+ const loadingAllThreadsRef = useRef(false);
2099
2237
  const selectModel = useCallback((modelId) => {
2100
2238
  if (!models.some((model) => model.id === modelId)) return;
2101
2239
  onModelChange(modelId);
2102
2240
  }, [models, onModelChange]);
2103
- const selectThread = useCallback(async (thread) => {
2241
+ const selectThread = useCallback(async (thread, reportError = true) => {
2104
2242
  setLoadingThread(true);
2105
2243
  setThreadError(null);
2106
2244
  try {
2107
2245
  const transcript = await threadClient.read(thread.id);
2108
2246
  setSelectedThread(transcript.thread);
2109
2247
  setInitialMessages(transcript.messages);
2248
+ setNextMessageCursor(transcript.nextCursor);
2110
2249
  setHistoryOpen(false);
2111
2250
  if (storageKey) window.localStorage.setItem(storageKey, thread.id);
2251
+ return transcript.thread;
2112
2252
  } catch (error) {
2113
- setThreadError(parseSurveyAssistantError(error).detail ?? t("errors.fallbackDetail"));
2253
+ if (reportError) setThreadError(parseSurveyAssistantError(error).detail ?? t("errors.fallbackDetail"));
2254
+ return null;
2114
2255
  } finally {
2115
2256
  setLoadingThread(false);
2116
2257
  }
@@ -2124,9 +2265,10 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2124
2265
  setThreadError(null);
2125
2266
  try {
2126
2267
  const thread = await threadClient.create(launch?.title);
2127
- setThreads((current) => [thread, ...current]);
2268
+ setThreads((current) => mergeThreads(current, [thread]));
2128
2269
  setSelectedThread(thread);
2129
2270
  setInitialMessages([]);
2271
+ setNextMessageCursor(void 0);
2130
2272
  setHistoryOpen(false);
2131
2273
  if (storageKey) window.localStorage.setItem(storageKey, thread.id);
2132
2274
  return thread;
@@ -2144,11 +2286,21 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2144
2286
  ]);
2145
2287
  useEffect(() => {
2146
2288
  let cancelled = false;
2147
- threadClient.list().then(async (availableThreads) => {
2289
+ listThreadPage(threadClient).then(async (page) => {
2148
2290
  if (cancelled) return;
2291
+ const availableThreads = page.threads;
2149
2292
  setThreads(availableThreads);
2293
+ setNextThreadCursor(page.nextCursor);
2150
2294
  const rememberedId = storageKey ? window.localStorage.getItem(storageKey) : null;
2151
- const initialThread = availableThreads.find((thread) => thread.id === rememberedId) ?? availableThreads[0];
2295
+ const rememberedThread = availableThreads.find((thread) => thread.id === rememberedId);
2296
+ if (rememberedId && !rememberedThread) {
2297
+ const restored = await selectThread({ id: rememberedId }, false);
2298
+ if (restored) {
2299
+ setThreads((current) => mergeThreads(current, [restored]));
2300
+ return;
2301
+ }
2302
+ }
2303
+ const initialThread = rememberedThread ?? availableThreads[0];
2152
2304
  if (initialThread) await selectThread(initialThread);
2153
2305
  else await createThread();
2154
2306
  }).catch((error) => {
@@ -2167,6 +2319,66 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2167
2319
  t,
2168
2320
  threadClient
2169
2321
  ]);
2322
+ const loadOlderThreads = useCallback(async () => {
2323
+ if (!nextThreadCursor || loadingOlderThreads) return;
2324
+ setLoadingOlderThreads(true);
2325
+ try {
2326
+ const page = await listThreadPage(threadClient, nextThreadCursor);
2327
+ setThreads((current) => mergeThreads(current, page.threads));
2328
+ setNextThreadCursor(page.nextCursor);
2329
+ } catch (error) {
2330
+ setThreadError(parseSurveyAssistantError(error).detail ?? t("errors.fallbackDetail"));
2331
+ } finally {
2332
+ setLoadingOlderThreads(false);
2333
+ }
2334
+ }, [
2335
+ loadingOlderThreads,
2336
+ nextThreadCursor,
2337
+ t,
2338
+ threadClient
2339
+ ]);
2340
+ const loadEarlierMessages = useCallback(async () => {
2341
+ if (!selectedThread || !nextMessageCursor) return [];
2342
+ try {
2343
+ const transcript = await threadClient.read(selectedThread.id, nextMessageCursor);
2344
+ setNextMessageCursor(transcript.nextCursor);
2345
+ return transcript.messages;
2346
+ } catch (error) {
2347
+ setThreadError(parseSurveyAssistantError(error).detail ?? t("errors.fallbackDetail"));
2348
+ return [];
2349
+ }
2350
+ }, [
2351
+ nextMessageCursor,
2352
+ selectedThread,
2353
+ t,
2354
+ threadClient
2355
+ ]);
2356
+ const loadAllThreadsForSearch = useCallback(async () => {
2357
+ if (!nextThreadCursor || loadingAllThreadsRef.current) return;
2358
+ loadingAllThreadsRef.current = true;
2359
+ setLoadingOlderThreads(true);
2360
+ try {
2361
+ let cursor = nextThreadCursor;
2362
+ while (cursor) {
2363
+ const page = await listThreadPage(threadClient, cursor);
2364
+ setThreads((current) => mergeThreads(current, page.threads));
2365
+ cursor = page.nextCursor;
2366
+ setNextThreadCursor(cursor);
2367
+ }
2368
+ } catch (error) {
2369
+ setThreadError(parseSurveyAssistantError(error).detail ?? t("errors.fallbackDetail"));
2370
+ } finally {
2371
+ loadingAllThreadsRef.current = false;
2372
+ setLoadingOlderThreads(false);
2373
+ }
2374
+ }, [
2375
+ nextThreadCursor,
2376
+ t,
2377
+ threadClient
2378
+ ]);
2379
+ useEffect(() => {
2380
+ if (threadSearch.trim()) loadAllThreadsForSearch();
2381
+ }, [loadAllThreadsForSearch, threadSearch]);
2170
2382
  useEffect(() => {
2171
2383
  if (!threadLauncher || loadingThread) return;
2172
2384
  return connectSurveyAssistantThreadLauncher(threadLauncher, async (launch) => {
@@ -2274,9 +2486,9 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2274
2486
  }, [initialLaunch]);
2275
2487
  const refreshThreadList = useCallback(async (reportError) => {
2276
2488
  try {
2277
- const availableThreads = await threadClient.list();
2278
- setThreads(availableThreads);
2279
- const current = availableThreads.find((thread) => thread.id === selectedThread?.id);
2489
+ const page = await listThreadPage(threadClient);
2490
+ setThreads((current) => mergeRecentThreadPage(current, page.threads));
2491
+ const current = page.threads.find((thread) => thread.id === selectedThread?.id);
2280
2492
  if (current && !editingThreadId) setSelectedThread((selected) => {
2281
2493
  if (!selected || selected.id !== current.id) return selected;
2282
2494
  return selected.title === current.title && selected.titleMode === current.titleMode && selected.updatedAt === current.updatedAt ? selected : current;
@@ -2352,6 +2564,8 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2352
2564
  children: [/* @__PURE__ */ jsx(LoaderCircleIcon, { className: "mr-2 size-4 animate-spin" }), t("panel.loadingChats")]
2353
2565
  })]
2354
2566
  });
2567
+ const normalizedThreadSearch = threadSearch.trim().toLocaleLowerCase();
2568
+ const visibleThreads = normalizedThreadSearch ? threads.filter((thread) => thread.title.toLocaleLowerCase().includes(normalizedThreadSearch)) : threads;
2355
2569
  const threadControls = /* @__PURE__ */ jsxs("div", {
2356
2570
  className: "relative flex shrink-0 items-center gap-1",
2357
2571
  ref: historyControlsRef,
@@ -2377,65 +2591,92 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2377
2591
  }),
2378
2592
  historyOpen && /* @__PURE__ */ jsxs("div", {
2379
2593
  className: "absolute top-10 right-0 z-30 w-72 overflow-hidden rounded-lg border border-border bg-popover text-popover-foreground shadow-lg",
2380
- children: [/* @__PURE__ */ jsx("div", {
2381
- className: "border-b border-border px-3 py-2 font-medium text-xs",
2382
- children: t("threads.recent")
2383
- }), /* @__PURE__ */ jsx("div", {
2384
- className: "max-h-72 overflow-y-auto p-1",
2385
- children: threads.map((thread) => /* @__PURE__ */ jsxs("div", {
2386
- className: cn("group flex min-h-10 items-center gap-1 rounded-md px-2", thread.id === selectedThread.id ? "bg-muted" : "hover:bg-muted/60"),
2594
+ children: [
2595
+ /* @__PURE__ */ jsx("div", {
2596
+ className: "border-b border-border px-3 py-2 font-medium text-xs",
2597
+ children: t("threads.recent")
2598
+ }),
2599
+ /* @__PURE__ */ jsxs("div", {
2600
+ className: "relative border-b border-border p-2",
2601
+ children: [/* @__PURE__ */ jsx(SearchIcon, { className: "pointer-events-none absolute top-1/2 left-4 size-3.5 -translate-y-1/2 text-muted-foreground" }), /* @__PURE__ */ jsx("input", {
2602
+ "aria-label": t("threads.search"),
2603
+ className: "h-8 w-full rounded-md border border-input bg-background pr-2 pl-8 text-xs outline-none placeholder:text-muted-foreground focus:ring-1 focus:ring-ring",
2604
+ onChange: (event) => setThreadSearch(event.currentTarget.value),
2605
+ placeholder: t("threads.search"),
2606
+ type: "search",
2607
+ value: threadSearch
2608
+ })]
2609
+ }),
2610
+ /* @__PURE__ */ jsxs("div", {
2611
+ className: "max-h-72 overflow-y-auto p-1",
2387
2612
  children: [
2388
- editingThreadId === thread.id ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("input", {
2389
- autoFocus: true,
2390
- 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",
2391
- onBlur: () => void commitEditingThread(),
2392
- onChange: (event) => setEditingTitle(event.currentTarget.value),
2393
- onKeyDown: (event) => {
2394
- if (event.key === "Enter") {
2395
- event.preventDefault();
2396
- commitEditingThread();
2397
- }
2398
- if (event.key === "Escape") {
2399
- event.preventDefault();
2400
- event.stopPropagation();
2401
- cancelEditingThread();
2402
- setHistoryOpen(false);
2403
- }
2404
- },
2405
- value: editingTitle
2406
- }), thread.titleMode === "manual" && /* @__PURE__ */ jsxs("button", {
2407
- "aria-label": t("threads.automaticTitle"),
2408
- 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",
2409
- onClick: () => void setAutomaticTitle(thread),
2410
- onMouseDown: (event) => event.preventDefault(),
2411
- title: t("threads.automaticTitle"),
2412
- type: "button",
2413
- children: [/* @__PURE__ */ jsx(SparklesIcon, { className: "size-3" }), /* @__PURE__ */ jsx("span", { children: t("threads.automaticShort") })]
2414
- })] }) : /* @__PURE__ */ jsx("button", {
2415
- className: "min-w-0 flex-1 truncate py-2 text-left text-xs",
2416
- onClick: () => void selectThread(thread),
2417
- type: "button",
2418
- children: thread.title
2419
- }),
2420
- /* @__PURE__ */ jsx("button", {
2421
- "aria-label": t("threads.rename", { title: thread.title }),
2422
- 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",
2423
- onClick: () => beginEditingThread(thread),
2424
- title: t("threads.renameTitle"),
2425
- type: "button",
2426
- children: /* @__PURE__ */ jsx(PencilIcon, { className: "size-3.5" })
2613
+ visibleThreads.map((thread) => /* @__PURE__ */ jsxs("div", {
2614
+ className: cn("group flex min-h-10 items-center gap-1 rounded-md px-2", thread.id === selectedThread.id ? "bg-muted" : "hover:bg-muted/60"),
2615
+ children: [
2616
+ editingThreadId === thread.id ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("input", {
2617
+ autoFocus: true,
2618
+ className: "min-w-0 flex-1 rounded border border-input bg-background px-1.5 py-1 text-xs outline-none focus:ring-1 focus:ring-ring",
2619
+ onBlur: () => void commitEditingThread(),
2620
+ onChange: (event) => setEditingTitle(event.currentTarget.value),
2621
+ onKeyDown: (event) => {
2622
+ if (event.key === "Enter") {
2623
+ event.preventDefault();
2624
+ commitEditingThread();
2625
+ }
2626
+ if (event.key === "Escape") {
2627
+ event.preventDefault();
2628
+ event.stopPropagation();
2629
+ cancelEditingThread();
2630
+ setHistoryOpen(false);
2631
+ }
2632
+ },
2633
+ value: editingTitle
2634
+ }), thread.titleMode === "manual" && /* @__PURE__ */ jsxs("button", {
2635
+ "aria-label": t("threads.automaticTitle"),
2636
+ className: "flex h-7 shrink-0 items-center gap-1 rounded px-1.5 text-muted-foreground text-xs hover:bg-background hover:text-foreground",
2637
+ onClick: () => void setAutomaticTitle(thread),
2638
+ onMouseDown: (event) => event.preventDefault(),
2639
+ title: t("threads.automaticTitle"),
2640
+ type: "button",
2641
+ children: [/* @__PURE__ */ jsx(SparklesIcon, { className: "size-3" }), /* @__PURE__ */ jsx("span", { children: t("threads.automaticShort") })]
2642
+ })] }) : /* @__PURE__ */ jsx("button", {
2643
+ className: "min-w-0 flex-1 truncate py-2 text-left text-xs",
2644
+ onClick: () => void selectThread(thread),
2645
+ type: "button",
2646
+ children: thread.title
2647
+ }),
2648
+ /* @__PURE__ */ jsx("button", {
2649
+ "aria-label": t("threads.rename", { title: thread.title }),
2650
+ className: "flex size-7 shrink-0 items-center justify-center rounded text-muted-foreground opacity-0 hover:bg-background hover:text-foreground group-hover:opacity-100",
2651
+ onClick: () => beginEditingThread(thread),
2652
+ title: t("threads.renameTitle"),
2653
+ type: "button",
2654
+ children: /* @__PURE__ */ jsx(PencilIcon, { className: "size-3.5" })
2655
+ }),
2656
+ /* @__PURE__ */ jsx("button", {
2657
+ "aria-label": t("threads.delete", { title: thread.title }),
2658
+ className: "flex size-7 shrink-0 items-center justify-center rounded text-muted-foreground opacity-0 hover:bg-background hover:text-foreground group-hover:opacity-100",
2659
+ onClick: () => void deleteThread(thread),
2660
+ title: t("threads.deleteTitle"),
2661
+ type: "button",
2662
+ children: /* @__PURE__ */ jsx(Trash2Icon, { className: "size-3.5" })
2663
+ })
2664
+ ]
2665
+ }, thread.id)),
2666
+ visibleThreads.length === 0 && !loadingOlderThreads && /* @__PURE__ */ jsx("div", {
2667
+ className: "px-3 py-5 text-center text-muted-foreground text-xs",
2668
+ children: t("threads.noMatches")
2427
2669
  }),
2428
- /* @__PURE__ */ jsx("button", {
2429
- "aria-label": t("threads.delete", { title: thread.title }),
2430
- 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",
2431
- onClick: () => void deleteThread(thread),
2432
- title: t("threads.deleteTitle"),
2670
+ (nextThreadCursor || loadingOlderThreads) && /* @__PURE__ */ jsx("button", {
2671
+ className: "flex h-9 w-full items-center justify-center rounded-md text-muted-foreground text-xs hover:bg-muted hover:text-foreground disabled:pointer-events-none",
2672
+ disabled: loadingOlderThreads || Boolean(normalizedThreadSearch),
2673
+ onClick: () => void loadOlderThreads(),
2433
2674
  type: "button",
2434
- children: /* @__PURE__ */ jsx(Trash2Icon, { className: "size-3.5" })
2675
+ children: loadingOlderThreads ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(LoaderCircleIcon, { className: "mr-1.5 size-3.5 animate-spin" }), normalizedThreadSearch ? t("threads.searching") : t("threads.loadingOlder")] }) : t("threads.loadOlder")
2435
2676
  })
2436
2677
  ]
2437
- }, thread.id))
2438
- })]
2678
+ })
2679
+ ]
2439
2680
  })
2440
2681
  ]
2441
2682
  });
@@ -2444,6 +2685,7 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2444
2685
  children: [/* @__PURE__ */ createElement(SurveyAssistantChatPanel, {
2445
2686
  ...sessionProps,
2446
2687
  embedded,
2688
+ hasEarlierMessages: Boolean(nextMessageCursor),
2447
2689
  initialLaunch: initialLaunch?.input,
2448
2690
  initialMessages,
2449
2691
  key: selectedThread.id,
@@ -2452,6 +2694,7 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2452
2694
  onInitialLaunchHandled: handleInitialLaunchHandled,
2453
2695
  onModelChange: selectModel,
2454
2696
  onFirstSubmit: handleFirstSubmit,
2697
+ onLoadEarlierMessages: loadEarlierMessages,
2455
2698
  onTurnSettled: handleTurnSettled,
2456
2699
  onTurnActiveChange: handleTurnActiveChange,
2457
2700
  resourceId,