@case-framework/survey-assistant 0.3.0 → 0.5.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 (79) hide show
  1. package/README.md +20 -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-DbrJxxOh.d.mts} +5 -2
  5. package/dist/constants-DbrJxxOh.d.mts.map +1 -0
  6. package/dist/{controller-proxy-DmBdzqKs.d.mts → controller-proxy-BdKF2yrq.d.mts} +2 -2
  7. package/dist/{controller-proxy-DmBdzqKs.d.mts.map → controller-proxy-BdKF2yrq.d.mts.map} +1 -1
  8. package/dist/{controller-proxy-CpkQRezb.mjs → controller-proxy-jWIPOZAv.mjs} +17 -8
  9. package/dist/controller-proxy-jWIPOZAv.mjs.map +1 -0
  10. package/dist/{engine-CWTuN3o2.mjs → engine-DcSdLRle.mjs} +172 -12
  11. package/dist/engine-DcSdLRle.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-BApnIiCI.d.mts} +7 -4
  17. package/dist/index-BApnIiCI.d.mts.map +1 -0
  18. package/dist/{index-Bo4dHAo8.d.mts → index-Da_Hb9-1.d.mts} +28 -4
  19. package/dist/index-Da_Hb9-1.d.mts.map +1 -0
  20. package/dist/{index-DZk20e3k.d.mts → index-DhNQKOwr.d.mts} +9 -5
  21. package/dist/index-DhNQKOwr.d.mts.map +1 -0
  22. package/dist/{index-Dxgg0-D_.d.mts → index-dV6df0W3.d.mts} +11 -5
  23. package/dist/index-dV6df0W3.d.mts.map +1 -0
  24. package/dist/{memory-thread-repository-acbz6HZJ.mjs → memory-thread-repository-CvYZwni6.mjs} +87 -28
  25. package/dist/memory-thread-repository-CvYZwni6.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-D1sC54ZA.mjs} +3 -3
  37. package/dist/request-context-D1sC54ZA.mjs.map +1 -0
  38. package/dist/server-agent.d.mts +8 -6
  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 +354 -7
  43. package/dist/server-runtime.d.mts.map +1 -1
  44. package/dist/server-runtime.mjs +361 -24
  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 +7 -7
  52. package/dist/server.d.mts.map +1 -1
  53. package/dist/server.mjs +2 -2
  54. package/dist/storage-postgres.mjs +3 -0
  55. package/dist/storage-postgres.mjs.map +1 -1
  56. package/dist/{tasks-DQgL449A.d.mts → tasks-CCtxjhwh.d.mts} +3 -3
  57. package/dist/{tasks-DQgL449A.d.mts.map → tasks-CCtxjhwh.d.mts.map} +1 -1
  58. package/dist/{thread-handlers-CeYl45xG.d.mts → thread-handlers-Bt9cDo-F.d.mts} +21 -6
  59. package/dist/{thread-handlers-CeYl45xG.d.mts.map → thread-handlers-Bt9cDo-F.d.mts.map} +1 -1
  60. package/dist/{tools-Dg9HZrYU.mjs → tools-3tq-XqI0.mjs} +31 -6
  61. package/dist/{tools-Dg9HZrYU.mjs.map → tools-3tq-XqI0.mjs.map} +1 -1
  62. package/dist/ui.css +1 -1
  63. package/dist/ui.d.mts +3 -3
  64. package/dist/ui.d.mts.map +1 -1
  65. package/dist/ui.mjs +438 -140
  66. package/dist/ui.mjs.map +1 -1
  67. package/docs/integration.md +113 -58
  68. package/package.json +3 -3
  69. package/dist/constants-DrGZXtQf.d.mts.map +0 -1
  70. package/dist/controller-proxy-CpkQRezb.mjs.map +0 -1
  71. package/dist/engine-CWTuN3o2.mjs.map +0 -1
  72. package/dist/index-Bo4dHAo8.d.mts.map +0 -1
  73. package/dist/index-D4OitUg4.d.mts.map +0 -1
  74. package/dist/index-DZk20e3k.d.mts.map +0 -1
  75. package/dist/index-Dxgg0-D_.d.mts.map +0 -1
  76. package/dist/memory-thread-repository-acbz6HZJ.mjs.map +0 -1
  77. package/dist/protocol-DATvkr1S.mjs.map +0 -1
  78. package/dist/react-Br5YE8H-.mjs.map +0 -1
  79. 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",
@@ -1350,27 +1408,69 @@ const getPersistedMessageTurnStatus = (message) => {
1350
1408
  }
1351
1409
  };
1352
1410
  //#endregion
1411
+ //#region src/ui/initial-thread-load.ts
1412
+ const getSurveyAssistantInitialThreadLoadState = ({ error, hasSelectedThread, loading }) => {
1413
+ if (loading) return "loading";
1414
+ if (!hasSelectedThread) return error ? "error" : "loading";
1415
+ return null;
1416
+ };
1417
+ //#endregion
1353
1418
  //#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")
1419
+ const isRootSurveyItem = (item) => typeof item.config === "object" && item.config !== null && item.config.isRoot === true;
1420
+ const getAssistantEmptyStateContext = (controller) => {
1421
+ try {
1422
+ const snapshot = controller?.getSnapshot();
1423
+ if (!snapshot) return "survey";
1424
+ const surveyItems = snapshot.survey.surveyItems.filter((item) => !isRootSurveyItem(item));
1425
+ if (surveyItems.length === 0) return "new-survey";
1426
+ if (surveyItems.some((item) => item.id === snapshot.ui.selectedItemId)) return "selected-item";
1427
+ return "survey";
1428
+ } catch {
1429
+ return "survey";
1372
1430
  }
1373
- ];
1431
+ };
1432
+ const getAssistantQuickPrompts = (t, context) => {
1433
+ if (context === "new-survey") return [
1434
+ {
1435
+ label: t("quickActions.draftSurvey.label"),
1436
+ description: t("quickActions.draftSurvey.description"),
1437
+ icon: WandSparklesIcon,
1438
+ prompt: t("quickActions.draftSurvey.prompt")
1439
+ },
1440
+ {
1441
+ label: t("quickActions.useBrief.label"),
1442
+ description: t("quickActions.useBrief.description"),
1443
+ icon: FileIcon,
1444
+ prompt: t("quickActions.useBrief.prompt")
1445
+ },
1446
+ {
1447
+ label: t("quickActions.planSurvey.label"),
1448
+ description: t("quickActions.planSurvey.description"),
1449
+ icon: ListChecksIcon,
1450
+ prompt: t("quickActions.planSurvey.prompt")
1451
+ }
1452
+ ];
1453
+ return [
1454
+ {
1455
+ label: t("quickActions.review.label"),
1456
+ description: t("quickActions.review.description"),
1457
+ icon: ListChecksIcon,
1458
+ prompt: t("quickActions.review.prompt")
1459
+ },
1460
+ {
1461
+ label: t("quickActions.create.label"),
1462
+ description: t("quickActions.create.description"),
1463
+ icon: MessageSquarePlusIcon,
1464
+ prompt: t("quickActions.create.prompt")
1465
+ },
1466
+ {
1467
+ label: t(context === "selected-item" ? "quickActions.refineSelection.label" : "quickActions.refine.label"),
1468
+ description: t(context === "selected-item" ? "quickActions.refineSelection.description" : "quickActions.refine.description"),
1469
+ icon: WandSparklesIcon,
1470
+ prompt: t(context === "selected-item" ? "quickActions.refineSelection.prompt" : "quickActions.refine.prompt")
1471
+ }
1472
+ ];
1473
+ };
1374
1474
  const isSurveyAssistantAttachmentReferencePart = (part) => {
1375
1475
  if (part.type !== "data-survey-assistant-attachment" || typeof part.data !== "object" || part.data === null) return false;
1376
1476
  const data = part.data;
@@ -1399,6 +1499,7 @@ const getAssistantErrorPresentation = (error, t) => {
1399
1499
  title: t(`errors.${parsed.code}.title`),
1400
1500
  description: parsed.detail ?? t("errors.fallbackDetail"),
1401
1501
  hint: t(`errors.${parsed.code}.hint`),
1502
+ retryable: parsed.retryable ?? (parsed.code === "rate_limited" || parsed.code === "request_failed"),
1402
1503
  tone: parsed.code === "model_unavailable" || parsed.code === "rate_limited" ? "warning" : "error"
1403
1504
  };
1404
1505
  };
@@ -1808,10 +1909,11 @@ const SurveyAssistantHeaderIdentity = ({ indicatorClassName, status, title }) =>
1808
1909
  })]
1809
1910
  })]
1810
1911
  });
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 }) => {
1912
+ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = false, focusPromptOnMount = false, hasEarlierMessages, initialLaunch, initialMessages, modelId, models, onInitialLaunchHandled, onModelChange, onFirstSubmit, onLoadEarlierMessages, onTurnSettled, onApplyReceipts, onSettleApply, onReviewChangeSets, onTurnActiveChange, resourceId, surveyId, threadControls, threadId, threadTitle }) => {
1812
1913
  const t = useSurveyAssistantTranslation();
1813
- const assistantQuickPrompts = getAssistantQuickPrompts(t);
1814
- const { applyResultsByChangeId, authorizationError, dismissError, error, isStreaming, latestTurnOutcome, messages, state, status, stop, submit, retry } = useSurveyAssistantSession({
1914
+ const emptyStateContext = getAssistantEmptyStateContext(controller);
1915
+ const assistantQuickPrompts = getAssistantQuickPrompts(t, emptyStateContext);
1916
+ const { applyResultsByChangeId, authorizationError, dismissError, error, isStreaming, latestTurnOutcome, messages, prependMessages, state, status, stop, submit, retry } = useSurveyAssistantSession({
1815
1917
  api,
1816
1918
  applyPolicy,
1817
1919
  controller,
@@ -1836,6 +1938,20 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1836
1938
  const promptTextareaRef = useRef(null);
1837
1939
  const shouldRestorePromptFocusRef = useRef(false);
1838
1940
  const turnWasActiveRef = useRef(false);
1941
+ const [loadingEarlierMessages, setLoadingEarlierMessages] = useState(false);
1942
+ const loadEarlierMessages = useCallback(async () => {
1943
+ if (loadingEarlierMessages) return;
1944
+ setLoadingEarlierMessages(true);
1945
+ try {
1946
+ prependMessages(await onLoadEarlierMessages());
1947
+ } finally {
1948
+ setLoadingEarlierMessages(false);
1949
+ }
1950
+ }, [
1951
+ loadingEarlierMessages,
1952
+ onLoadEarlierMessages,
1953
+ prependMessages
1954
+ ]);
1839
1955
  const isAssistantTurnSettled = !isStreaming && (state === "completed" || state === "failed" || state === "aborted" || state === "applied");
1840
1956
  useEffect(() => {
1841
1957
  if (!isAssistantTurnActive) {
@@ -1943,16 +2059,16 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1943
2059
  className: "mx-auto w-full max-w-sm",
1944
2060
  children: [
1945
2061
  /* @__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" })
2062
+ className: "mb-3 flex size-9 items-center justify-center rounded-xl border border-border bg-muted text-foreground shadow-xs",
2063
+ children: /* @__PURE__ */ jsx(SparklesIcon, { className: "size-4" })
1948
2064
  }),
1949
2065
  /* @__PURE__ */ jsx("h3", {
1950
2066
  className: "font-semibold text-foreground text-base tracking-tight",
1951
- children: t("empty.title")
2067
+ children: t(emptyStateContext === "new-survey" ? "empty.newSurveyTitle" : "empty.title")
1952
2068
  }),
1953
2069
  /* @__PURE__ */ jsx("p", {
1954
2070
  className: "mt-1 text-muted-foreground text-xs leading-relaxed",
1955
- children: t("empty.description")
2071
+ children: t(emptyStateContext === "new-survey" ? "empty.newSurveyDescription" : "empty.description")
1956
2072
  }),
1957
2073
  /* @__PURE__ */ jsx("div", {
1958
2074
  className: "mt-4 flex flex-col gap-1.5",
@@ -1981,40 +2097,50 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1981
2097
  })
1982
2098
  ]
1983
2099
  })
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, {
2100
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2101
+ hasEarlierMessages && /* @__PURE__ */ jsxs("button", {
2102
+ 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",
2103
+ disabled: loadingEarlierMessages || isAssistantTurnActive,
2104
+ onClick: () => void loadEarlierMessages(),
2105
+ type: "button",
2106
+ children: [loadingEarlierMessages && /* @__PURE__ */ jsx(LoaderCircleIcon, { className: "mr-1.5 size-3.5 animate-spin" }), loadingEarlierMessages ? t("conversation.loadingEarlier") : t("conversation.loadEarlier")]
2107
+ }),
2108
+ messages.map((message, index) => {
2109
+ const isLastMessage = index === messages.length - 1;
2110
+ const persistedTurnStatus = getPersistedMessageTurnStatus(message);
2111
+ const terminalTurnStatus = persistedTurnStatus === "aborted" || persistedTurnStatus === "failed" ? persistedTurnStatus : isLastMessage && (state === "aborted" || state === "failed") ? state : void 0;
2112
+ const shouldShowProgress = isAssistantTurnActive && isLastMessage && message.role === "assistant";
2113
+ return /* @__PURE__ */ jsx(Message, {
2114
+ className: "max-w-[96%] gap-1",
2115
+ from: message.role,
2116
+ children: /* @__PURE__ */ jsx(MessageContent, {
2117
+ className: cn(message.role === "assistant" && "w-full space-y-2"),
2118
+ children: hasRenderableParts(message) ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(SurveyAssistantMessageParts, {
2119
+ applyResultsByChangeId,
2120
+ isLastMessage,
2121
+ isStreaming: isAssistantTurnActive,
2122
+ message,
2123
+ showProgress: shouldShowProgress,
2124
+ terminalTurnStatus
2125
+ }), isLastMessage && latestTurnOutcome && /* @__PURE__ */ jsx(SurveyAssistantTurnOutcomeNotice, { ...latestTurnOutcome })] }) : shouldShowProgress ? /* @__PURE__ */ jsx(SurveyAssistantProgressIndicator, { message }) : isLastMessage && latestTurnOutcome ? /* @__PURE__ */ jsx(SurveyAssistantTurnOutcomeNotice, { ...latestTurnOutcome }) : state === "aborted" ? /* @__PURE__ */ jsx("span", {
2126
+ className: "text-muted-foreground text-sm",
2127
+ children: t("conversation.stopped")
2128
+ }) : /* @__PURE__ */ jsx("span", {
2129
+ className: "text-muted-foreground text-sm",
2130
+ children: t("conversation.incomplete")
2131
+ })
2132
+ })
2133
+ }, message.id);
2134
+ }),
2135
+ showPendingAssistantRow && /* @__PURE__ */ jsx(Message, {
1990
2136
  className: "max-w-[96%] gap-1",
1991
- from: message.role,
2137
+ from: "assistant",
1992
2138
  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
- })
2139
+ className: "w-full",
2140
+ children: /* @__PURE__ */ jsx(SurveyAssistantProgressIndicator, {})
2008
2141
  })
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
2142
  })
2017
- })] })
2143
+ ] })
2018
2144
  }), /* @__PURE__ */ jsx(ConversationScrollButton, { className: "bottom-2.5" })]
2019
2145
  }),
2020
2146
  showAssistantError && /* @__PURE__ */ jsxs("div", {
@@ -2037,7 +2163,7 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
2037
2163
  className: "mt-1 text-muted-foreground/80",
2038
2164
  children: assistantErrorPresentation.hint
2039
2165
  }),
2040
- /* @__PURE__ */ jsx("button", {
2166
+ assistantErrorPresentation.retryable && /* @__PURE__ */ jsx("button", {
2041
2167
  className: "mt-1.5 font-medium text-foreground text-xs hover:underline",
2042
2168
  onClick: retry,
2043
2169
  type: "button",
@@ -2068,6 +2194,7 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
2068
2194
  onSubmit: handleSubmit,
2069
2195
  status,
2070
2196
  textareaClassName: "min-h-11 max-h-28 px-3 pt-2.5 pb-1.5 transition-colors disabled:placeholder:text-muted-foreground/60",
2197
+ textareaProps: { autoFocus: focusPromptOnMount },
2071
2198
  textareaRef: promptTextareaRef
2072
2199
  })
2073
2200
  })
@@ -2081,36 +2208,61 @@ const createThreadTitle = (text, fallback) => {
2081
2208
  const words = normalized.split(" ").slice(0, 8).join(" ");
2082
2209
  return words.length > 64 ? `${words.slice(0, 61).trimEnd()}...` : words;
2083
2210
  };
2211
+ const mergeThreads = (current, incoming) => {
2212
+ const byId = new Map(current.map((thread) => [thread.id, thread]));
2213
+ for (const thread of incoming) byId.set(thread.id, thread);
2214
+ return [...byId.values()].sort((left, right) => Date.parse(right.updatedAt) - Date.parse(left.updatedAt));
2215
+ };
2216
+ const mergeRecentThreadPage = (current, recent) => {
2217
+ if (recent.length === 0) return [];
2218
+ const oldestRecentUpdate = Math.min(...recent.map((thread) => Date.parse(thread.updatedAt)));
2219
+ return mergeThreads(Number.isFinite(oldestRecentUpdate) ? current.filter((thread) => Date.parse(thread.updatedAt) <= oldestRecentUpdate) : current, recent);
2220
+ };
2221
+ const listThreadPage = async (client, cursor) => {
2222
+ if (client.listPage) return client.listPage(cursor ? { cursor } : void 0);
2223
+ return cursor ? { threads: [] } : { threads: await client.list() };
2224
+ };
2084
2225
  const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], onConfirmDeleteThread, onModelChange = () => {}, onTurnActiveChange: onAssistantTurnActiveChange, resourceId, selectedThreadStorageKey, surveyId, threadApi, threadClient: providedThreadClient, threadLauncher, ...sessionProps }) => {
2085
2226
  const t = useSurveyAssistantTranslation();
2086
2227
  const threadClient = useMemo(() => providedThreadClient ?? createSurveyAssistantThreadClient(threadApi), [providedThreadClient, threadApi]);
2087
2228
  const storageKey = selectedThreadStorageKey === false ? null : selectedThreadStorageKey ?? `${SELECTED_THREAD_STORAGE_KEY}:${encodeURIComponent(resourceId)}${surveyId ? `:${encodeURIComponent(surveyId)}` : ""}`;
2088
2229
  const [threads, setThreads] = useState([]);
2230
+ const [nextThreadCursor, setNextThreadCursor] = useState();
2089
2231
  const [selectedThread, setSelectedThread] = useState(null);
2090
2232
  const [initialMessages, setInitialMessages] = useState([]);
2233
+ const [nextMessageCursor, setNextMessageCursor] = useState();
2091
2234
  const [historyOpen, setHistoryOpen] = useState(false);
2235
+ const [threadSearch, setThreadSearch] = useState("");
2236
+ const [loadingOlderThreads, setLoadingOlderThreads] = useState(false);
2092
2237
  const [editingThreadId, setEditingThreadId] = useState(null);
2093
2238
  const [editingTitle, setEditingTitle] = useState("");
2094
2239
  const [threadError, setThreadError] = useState(null);
2095
2240
  const [loadingThread, setLoadingThread] = useState(true);
2241
+ const [threadLoadAttempt, setThreadLoadAttempt] = useState(0);
2096
2242
  const [assistantTurnActive, setAssistantTurnActive] = useState(false);
2097
2243
  const [initialLaunch, setInitialLaunch] = useState();
2244
+ const [promptFocusThreadId, setPromptFocusThreadId] = useState();
2098
2245
  const historyControlsRef = useRef(null);
2246
+ const loadingAllThreadsRef = useRef(false);
2099
2247
  const selectModel = useCallback((modelId) => {
2100
2248
  if (!models.some((model) => model.id === modelId)) return;
2101
2249
  onModelChange(modelId);
2102
2250
  }, [models, onModelChange]);
2103
- const selectThread = useCallback(async (thread) => {
2251
+ const selectThread = useCallback(async (thread, reportError = true) => {
2252
+ setPromptFocusThreadId(void 0);
2104
2253
  setLoadingThread(true);
2105
2254
  setThreadError(null);
2106
2255
  try {
2107
2256
  const transcript = await threadClient.read(thread.id);
2108
2257
  setSelectedThread(transcript.thread);
2109
2258
  setInitialMessages(transcript.messages);
2259
+ setNextMessageCursor(transcript.nextCursor);
2110
2260
  setHistoryOpen(false);
2111
2261
  if (storageKey) window.localStorage.setItem(storageKey, thread.id);
2262
+ return transcript.thread;
2112
2263
  } catch (error) {
2113
- setThreadError(parseSurveyAssistantError(error).detail ?? t("errors.fallbackDetail"));
2264
+ if (reportError) setThreadError(parseSurveyAssistantError(error).detail ?? t("errors.fallbackDetail"));
2265
+ return null;
2114
2266
  } finally {
2115
2267
  setLoadingThread(false);
2116
2268
  }
@@ -2124,9 +2276,11 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2124
2276
  setThreadError(null);
2125
2277
  try {
2126
2278
  const thread = await threadClient.create(launch?.title);
2127
- setThreads((current) => [thread, ...current]);
2279
+ setPromptFocusThreadId(launch ? void 0 : thread.id);
2280
+ setThreads((current) => mergeThreads(current, [thread]));
2128
2281
  setSelectedThread(thread);
2129
2282
  setInitialMessages([]);
2283
+ setNextMessageCursor(void 0);
2130
2284
  setHistoryOpen(false);
2131
2285
  if (storageKey) window.localStorage.setItem(storageKey, thread.id);
2132
2286
  return thread;
@@ -2144,11 +2298,21 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2144
2298
  ]);
2145
2299
  useEffect(() => {
2146
2300
  let cancelled = false;
2147
- threadClient.list().then(async (availableThreads) => {
2301
+ listThreadPage(threadClient).then(async (page) => {
2148
2302
  if (cancelled) return;
2303
+ const availableThreads = page.threads;
2149
2304
  setThreads(availableThreads);
2305
+ setNextThreadCursor(page.nextCursor);
2150
2306
  const rememberedId = storageKey ? window.localStorage.getItem(storageKey) : null;
2151
- const initialThread = availableThreads.find((thread) => thread.id === rememberedId) ?? availableThreads[0];
2307
+ const rememberedThread = availableThreads.find((thread) => thread.id === rememberedId);
2308
+ if (rememberedId && !rememberedThread) {
2309
+ const restored = await selectThread({ id: rememberedId }, false);
2310
+ if (restored) {
2311
+ setThreads((current) => mergeThreads(current, [restored]));
2312
+ return;
2313
+ }
2314
+ }
2315
+ const initialThread = rememberedThread ?? availableThreads[0];
2152
2316
  if (initialThread) await selectThread(initialThread);
2153
2317
  else await createThread();
2154
2318
  }).catch((error) => {
@@ -2165,8 +2329,74 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2165
2329
  selectThread,
2166
2330
  storageKey,
2167
2331
  t,
2332
+ threadClient,
2333
+ threadLoadAttempt
2334
+ ]);
2335
+ const retryInitialThreadLoad = useCallback(() => {
2336
+ setThreadError(null);
2337
+ setLoadingThread(true);
2338
+ setThreadLoadAttempt((attempt) => attempt + 1);
2339
+ }, []);
2340
+ const loadOlderThreads = useCallback(async () => {
2341
+ if (!nextThreadCursor || loadingOlderThreads) return;
2342
+ setLoadingOlderThreads(true);
2343
+ try {
2344
+ const page = await listThreadPage(threadClient, nextThreadCursor);
2345
+ setThreads((current) => mergeThreads(current, page.threads));
2346
+ setNextThreadCursor(page.nextCursor);
2347
+ } catch (error) {
2348
+ setThreadError(parseSurveyAssistantError(error).detail ?? t("errors.fallbackDetail"));
2349
+ } finally {
2350
+ setLoadingOlderThreads(false);
2351
+ }
2352
+ }, [
2353
+ loadingOlderThreads,
2354
+ nextThreadCursor,
2355
+ t,
2356
+ threadClient
2357
+ ]);
2358
+ const loadEarlierMessages = useCallback(async () => {
2359
+ if (!selectedThread || !nextMessageCursor) return [];
2360
+ try {
2361
+ const transcript = await threadClient.read(selectedThread.id, nextMessageCursor);
2362
+ setNextMessageCursor(transcript.nextCursor);
2363
+ return transcript.messages;
2364
+ } catch (error) {
2365
+ setThreadError(parseSurveyAssistantError(error).detail ?? t("errors.fallbackDetail"));
2366
+ return [];
2367
+ }
2368
+ }, [
2369
+ nextMessageCursor,
2370
+ selectedThread,
2371
+ t,
2372
+ threadClient
2373
+ ]);
2374
+ const loadAllThreadsForSearch = useCallback(async () => {
2375
+ if (!nextThreadCursor || loadingAllThreadsRef.current) return;
2376
+ loadingAllThreadsRef.current = true;
2377
+ setLoadingOlderThreads(true);
2378
+ try {
2379
+ let cursor = nextThreadCursor;
2380
+ while (cursor) {
2381
+ const page = await listThreadPage(threadClient, cursor);
2382
+ setThreads((current) => mergeThreads(current, page.threads));
2383
+ cursor = page.nextCursor;
2384
+ setNextThreadCursor(cursor);
2385
+ }
2386
+ } catch (error) {
2387
+ setThreadError(parseSurveyAssistantError(error).detail ?? t("errors.fallbackDetail"));
2388
+ } finally {
2389
+ loadingAllThreadsRef.current = false;
2390
+ setLoadingOlderThreads(false);
2391
+ }
2392
+ }, [
2393
+ nextThreadCursor,
2394
+ t,
2168
2395
  threadClient
2169
2396
  ]);
2397
+ useEffect(() => {
2398
+ if (threadSearch.trim()) loadAllThreadsForSearch();
2399
+ }, [loadAllThreadsForSearch, threadSearch]);
2170
2400
  useEffect(() => {
2171
2401
  if (!threadLauncher || loadingThread) return;
2172
2402
  return connectSurveyAssistantThreadLauncher(threadLauncher, async (launch) => {
@@ -2274,9 +2504,9 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2274
2504
  }, [initialLaunch]);
2275
2505
  const refreshThreadList = useCallback(async (reportError) => {
2276
2506
  try {
2277
- const availableThreads = await threadClient.list();
2278
- setThreads(availableThreads);
2279
- const current = availableThreads.find((thread) => thread.id === selectedThread?.id);
2507
+ const page = await listThreadPage(threadClient);
2508
+ setThreads((current) => mergeRecentThreadPage(current, page.threads));
2509
+ const current = page.threads.find((thread) => thread.id === selectedThread?.id);
2280
2510
  if (current && !editingThreadId) setSelectedThread((selected) => {
2281
2511
  if (!selected || selected.id !== current.id) return selected;
2282
2512
  return selected.title === current.title && selected.titleMode === current.titleMode && selected.updatedAt === current.updatedAt ? selected : current;
@@ -2331,6 +2561,13 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2331
2561
  commitEditingThread,
2332
2562
  historyOpen
2333
2563
  ]);
2564
+ useEffect(() => {
2565
+ if (!loadingThread && selectedThread?.id === promptFocusThreadId) setPromptFocusThreadId(void 0);
2566
+ }, [
2567
+ loadingThread,
2568
+ promptFocusThreadId,
2569
+ selectedThread?.id
2570
+ ]);
2334
2571
  useEffect(() => {
2335
2572
  const handleFocus = () => {
2336
2573
  refreshThreadList(false);
@@ -2338,20 +2575,51 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2338
2575
  window.addEventListener("focus", handleFocus);
2339
2576
  return () => window.removeEventListener("focus", handleFocus);
2340
2577
  }, [refreshThreadList]);
2341
- if (loadingThread || !selectedThread) return /* @__PURE__ */ jsxs("aside", {
2342
- className: cn("flex h-full min-h-0 flex-col overflow-visible bg-background", !embedded && "rounded-xl border border-border shadow-sm"),
2343
- children: [/* @__PURE__ */ jsx("header", {
2344
- className: "box-content flex h-9 shrink-0 items-center border-b border-border bg-background px-2.5",
2345
- children: /* @__PURE__ */ jsx(SurveyAssistantHeaderIdentity, {
2346
- indicatorClassName: "animate-pulse bg-blue-500",
2347
- status: t("panel.loadingChats"),
2348
- title: t("panel.title")
2349
- })
2350
- }), /* @__PURE__ */ jsxs("div", {
2351
- className: "flex min-h-0 flex-1 items-center justify-center text-muted-foreground text-sm",
2352
- children: [/* @__PURE__ */ jsx(LoaderCircleIcon, { className: "mr-2 size-4 animate-spin" }), t("panel.loadingChats")]
2353
- })]
2578
+ const initialThreadLoadState = getSurveyAssistantInitialThreadLoadState({
2579
+ error: threadError,
2580
+ hasSelectedThread: Boolean(selectedThread),
2581
+ loading: loadingThread
2354
2582
  });
2583
+ if (initialThreadLoadState || !selectedThread) {
2584
+ const initialLoadFailed = initialThreadLoadState === "error";
2585
+ return /* @__PURE__ */ jsxs("aside", {
2586
+ className: cn("flex h-full min-h-0 flex-col overflow-visible bg-background", !embedded && "rounded-xl border border-border shadow-sm"),
2587
+ children: [/* @__PURE__ */ jsx("header", {
2588
+ className: "box-content flex h-9 shrink-0 items-center border-b border-border bg-background px-2.5",
2589
+ children: /* @__PURE__ */ jsx(SurveyAssistantHeaderIdentity, {
2590
+ indicatorClassName: initialLoadFailed ? "bg-destructive" : "animate-pulse bg-blue-500",
2591
+ status: initialLoadFailed ? t("panel.controlsUnavailable") : t("panel.loadingChats"),
2592
+ title: t("panel.title")
2593
+ })
2594
+ }), /* @__PURE__ */ jsx("div", {
2595
+ className: "flex min-h-0 flex-1 items-center justify-center p-6 text-sm",
2596
+ children: initialLoadFailed ? /* @__PURE__ */ jsxs("div", {
2597
+ className: "max-w-sm text-center",
2598
+ children: [
2599
+ /* @__PURE__ */ jsx("div", {
2600
+ className: "font-medium text-foreground",
2601
+ children: t("panel.controlsUnavailable")
2602
+ }),
2603
+ /* @__PURE__ */ jsx("div", {
2604
+ className: "mt-1 text-muted-foreground",
2605
+ children: threadError
2606
+ }),
2607
+ /* @__PURE__ */ jsx("button", {
2608
+ className: "mt-3 rounded-md border border-border bg-background px-3 py-1.5 font-medium text-foreground hover:bg-muted",
2609
+ onClick: retryInitialThreadLoad,
2610
+ type: "button",
2611
+ children: t("errors.retry")
2612
+ })
2613
+ ]
2614
+ }) : /* @__PURE__ */ jsxs("div", {
2615
+ className: "flex items-center text-muted-foreground",
2616
+ children: [/* @__PURE__ */ jsx(LoaderCircleIcon, { className: "mr-2 size-4 animate-spin" }), t("panel.loadingChats")]
2617
+ })
2618
+ })]
2619
+ });
2620
+ }
2621
+ const normalizedThreadSearch = threadSearch.trim().toLocaleLowerCase();
2622
+ const visibleThreads = normalizedThreadSearch ? threads.filter((thread) => thread.title.toLocaleLowerCase().includes(normalizedThreadSearch)) : threads;
2355
2623
  const threadControls = /* @__PURE__ */ jsxs("div", {
2356
2624
  className: "relative flex shrink-0 items-center gap-1",
2357
2625
  ref: historyControlsRef,
@@ -2377,65 +2645,92 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2377
2645
  }),
2378
2646
  historyOpen && /* @__PURE__ */ jsxs("div", {
2379
2647
  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"),
2648
+ children: [
2649
+ /* @__PURE__ */ jsx("div", {
2650
+ className: "border-b border-border px-3 py-2 font-medium text-xs",
2651
+ children: t("threads.recent")
2652
+ }),
2653
+ /* @__PURE__ */ jsxs("div", {
2654
+ className: "relative border-b border-border p-2",
2655
+ 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", {
2656
+ "aria-label": t("threads.search"),
2657
+ 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",
2658
+ onChange: (event) => setThreadSearch(event.currentTarget.value),
2659
+ placeholder: t("threads.search"),
2660
+ type: "search",
2661
+ value: threadSearch
2662
+ })]
2663
+ }),
2664
+ /* @__PURE__ */ jsxs("div", {
2665
+ className: "max-h-72 overflow-y-auto p-1",
2387
2666
  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" })
2667
+ visibleThreads.map((thread) => /* @__PURE__ */ jsxs("div", {
2668
+ 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"),
2669
+ children: [
2670
+ editingThreadId === thread.id ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("input", {
2671
+ autoFocus: true,
2672
+ 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",
2673
+ onBlur: () => void commitEditingThread(),
2674
+ onChange: (event) => setEditingTitle(event.currentTarget.value),
2675
+ onKeyDown: (event) => {
2676
+ if (event.key === "Enter") {
2677
+ event.preventDefault();
2678
+ commitEditingThread();
2679
+ }
2680
+ if (event.key === "Escape") {
2681
+ event.preventDefault();
2682
+ event.stopPropagation();
2683
+ cancelEditingThread();
2684
+ setHistoryOpen(false);
2685
+ }
2686
+ },
2687
+ value: editingTitle
2688
+ }), thread.titleMode === "manual" && /* @__PURE__ */ jsxs("button", {
2689
+ "aria-label": t("threads.automaticTitle"),
2690
+ 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",
2691
+ onClick: () => void setAutomaticTitle(thread),
2692
+ onMouseDown: (event) => event.preventDefault(),
2693
+ title: t("threads.automaticTitle"),
2694
+ type: "button",
2695
+ children: [/* @__PURE__ */ jsx(SparklesIcon, { className: "size-3" }), /* @__PURE__ */ jsx("span", { children: t("threads.automaticShort") })]
2696
+ })] }) : /* @__PURE__ */ jsx("button", {
2697
+ className: "min-w-0 flex-1 truncate py-2 text-left text-xs",
2698
+ onClick: () => void selectThread(thread),
2699
+ type: "button",
2700
+ children: thread.title
2701
+ }),
2702
+ /* @__PURE__ */ jsx("button", {
2703
+ "aria-label": t("threads.rename", { title: thread.title }),
2704
+ 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",
2705
+ onClick: () => beginEditingThread(thread),
2706
+ title: t("threads.renameTitle"),
2707
+ type: "button",
2708
+ children: /* @__PURE__ */ jsx(PencilIcon, { className: "size-3.5" })
2709
+ }),
2710
+ /* @__PURE__ */ jsx("button", {
2711
+ "aria-label": t("threads.delete", { title: thread.title }),
2712
+ 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",
2713
+ onClick: () => void deleteThread(thread),
2714
+ title: t("threads.deleteTitle"),
2715
+ type: "button",
2716
+ children: /* @__PURE__ */ jsx(Trash2Icon, { className: "size-3.5" })
2717
+ })
2718
+ ]
2719
+ }, thread.id)),
2720
+ visibleThreads.length === 0 && !loadingOlderThreads && /* @__PURE__ */ jsx("div", {
2721
+ className: "px-3 py-5 text-center text-muted-foreground text-xs",
2722
+ children: t("threads.noMatches")
2427
2723
  }),
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"),
2724
+ (nextThreadCursor || loadingOlderThreads) && /* @__PURE__ */ jsx("button", {
2725
+ 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",
2726
+ disabled: loadingOlderThreads || Boolean(normalizedThreadSearch),
2727
+ onClick: () => void loadOlderThreads(),
2433
2728
  type: "button",
2434
- children: /* @__PURE__ */ jsx(Trash2Icon, { className: "size-3.5" })
2729
+ 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
2730
  })
2436
2731
  ]
2437
- }, thread.id))
2438
- })]
2732
+ })
2733
+ ]
2439
2734
  })
2440
2735
  ]
2441
2736
  });
@@ -2444,6 +2739,8 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2444
2739
  children: [/* @__PURE__ */ createElement(SurveyAssistantChatPanel, {
2445
2740
  ...sessionProps,
2446
2741
  embedded,
2742
+ focusPromptOnMount: selectedThread.id === promptFocusThreadId,
2743
+ hasEarlierMessages: Boolean(nextMessageCursor),
2447
2744
  initialLaunch: initialLaunch?.input,
2448
2745
  initialMessages,
2449
2746
  key: selectedThread.id,
@@ -2452,6 +2749,7 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2452
2749
  onInitialLaunchHandled: handleInitialLaunchHandled,
2453
2750
  onModelChange: selectModel,
2454
2751
  onFirstSubmit: handleFirstSubmit,
2752
+ onLoadEarlierMessages: loadEarlierMessages,
2455
2753
  onTurnSettled: handleTurnSettled,
2456
2754
  onTurnActiveChange: handleTurnActiveChange,
2457
2755
  resourceId,