@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.
- package/README.md +20 -4
- package/dist/capabilities-default.d.mts +1 -1
- package/dist/capabilities-default.mjs +1 -1
- package/dist/{constants-DrGZXtQf.d.mts → constants-DbrJxxOh.d.mts} +5 -2
- package/dist/constants-DbrJxxOh.d.mts.map +1 -0
- package/dist/{controller-proxy-DmBdzqKs.d.mts → controller-proxy-BdKF2yrq.d.mts} +2 -2
- package/dist/{controller-proxy-DmBdzqKs.d.mts.map → controller-proxy-BdKF2yrq.d.mts.map} +1 -1
- package/dist/{controller-proxy-CpkQRezb.mjs → controller-proxy-jWIPOZAv.mjs} +17 -8
- package/dist/controller-proxy-jWIPOZAv.mjs.map +1 -0
- package/dist/{engine-CWTuN3o2.mjs → engine-DcSdLRle.mjs} +172 -12
- package/dist/engine-DcSdLRle.mjs.map +1 -0
- package/dist/engine.d.mts +2 -2
- package/dist/engine.mjs +3 -3
- package/dist/{form-authoring-DCd9XfXf.mjs → form-authoring-BQh40yDb.mjs} +2 -2
- package/dist/{form-authoring-DCd9XfXf.mjs.map → form-authoring-BQh40yDb.mjs.map} +1 -1
- package/dist/{index-D4OitUg4.d.mts → index-BApnIiCI.d.mts} +7 -4
- package/dist/index-BApnIiCI.d.mts.map +1 -0
- package/dist/{index-Bo4dHAo8.d.mts → index-Da_Hb9-1.d.mts} +28 -4
- package/dist/index-Da_Hb9-1.d.mts.map +1 -0
- package/dist/{index-DZk20e3k.d.mts → index-DhNQKOwr.d.mts} +9 -5
- package/dist/index-DhNQKOwr.d.mts.map +1 -0
- package/dist/{index-Dxgg0-D_.d.mts → index-dV6df0W3.d.mts} +11 -5
- package/dist/index-dV6df0W3.d.mts.map +1 -0
- package/dist/{memory-thread-repository-acbz6HZJ.mjs → memory-thread-repository-CvYZwni6.mjs} +87 -28
- package/dist/memory-thread-repository-CvYZwni6.mjs.map +1 -0
- package/dist/{protocol-DATvkr1S.mjs → protocol-DJUP4gc0.mjs} +10 -4
- package/dist/protocol-DJUP4gc0.mjs.map +1 -0
- package/dist/protocol.d.mts +1 -1
- package/dist/protocol.mjs +1 -1
- package/dist/{react-Br5YE8H-.mjs → react-CY6N7lVi.mjs} +25 -5
- package/dist/react-CY6N7lVi.mjs.map +1 -0
- package/dist/react-integration.d.mts +1 -1
- package/dist/react-integration.mjs +1 -1
- package/dist/react.d.mts +2 -2
- package/dist/react.mjs +2 -2
- package/dist/{request-context-BEjnaaFr.mjs → request-context-D1sC54ZA.mjs} +3 -3
- package/dist/request-context-D1sC54ZA.mjs.map +1 -0
- package/dist/server-agent.d.mts +8 -6
- package/dist/server-agent.d.mts.map +1 -1
- package/dist/server-agent.mjs +4 -3
- package/dist/server-agent.mjs.map +1 -1
- package/dist/server-runtime.d.mts +354 -7
- package/dist/server-runtime.d.mts.map +1 -1
- package/dist/server-runtime.mjs +361 -24
- package/dist/server-runtime.mjs.map +1 -1
- package/dist/server-tasks.d.mts +1 -1
- package/dist/server-tasks.mjs +5 -4
- package/dist/server-tasks.mjs.map +1 -1
- package/dist/server-tools.d.mts +1 -1
- package/dist/server-tools.mjs +1 -1
- package/dist/server.d.mts +7 -7
- package/dist/server.d.mts.map +1 -1
- package/dist/server.mjs +2 -2
- package/dist/storage-postgres.mjs +3 -0
- package/dist/storage-postgres.mjs.map +1 -1
- package/dist/{tasks-DQgL449A.d.mts → tasks-CCtxjhwh.d.mts} +3 -3
- package/dist/{tasks-DQgL449A.d.mts.map → tasks-CCtxjhwh.d.mts.map} +1 -1
- package/dist/{thread-handlers-CeYl45xG.d.mts → thread-handlers-Bt9cDo-F.d.mts} +21 -6
- package/dist/{thread-handlers-CeYl45xG.d.mts.map → thread-handlers-Bt9cDo-F.d.mts.map} +1 -1
- package/dist/{tools-Dg9HZrYU.mjs → tools-3tq-XqI0.mjs} +31 -6
- package/dist/{tools-Dg9HZrYU.mjs.map → tools-3tq-XqI0.mjs.map} +1 -1
- package/dist/ui.css +1 -1
- package/dist/ui.d.mts +3 -3
- package/dist/ui.d.mts.map +1 -1
- package/dist/ui.mjs +438 -140
- package/dist/ui.mjs.map +1 -1
- package/docs/integration.md +113 -58
- package/package.json +3 -3
- package/dist/constants-DrGZXtQf.d.mts.map +0 -1
- package/dist/controller-proxy-CpkQRezb.mjs.map +0 -1
- package/dist/engine-CWTuN3o2.mjs.map +0 -1
- package/dist/index-Bo4dHAo8.d.mts.map +0 -1
- package/dist/index-D4OitUg4.d.mts.map +0 -1
- package/dist/index-DZk20e3k.d.mts.map +0 -1
- package/dist/index-Dxgg0-D_.d.mts.map +0 -1
- package/dist/memory-thread-repository-acbz6HZJ.mjs.map +0 -1
- package/dist/protocol-DATvkr1S.mjs.map +0 -1
- package/dist/react-Br5YE8H-.mjs.map +0 -1
- 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-
|
|
3
|
-
import { f as unwrapSurveyAssistantToolOutput, i as createSurveyAssistantThreadClient, l as parseSurveyChangeSet, p as isSurveyAssistantTurnActive, r as useSurveyAssistantModelCatalog, t as useSurveyAssistantSession } from "./react-
|
|
4
|
-
import { o as connectSurveyAssistantThreadLauncher } from "./controller-proxy-
|
|
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: "
|
|
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: "
|
|
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
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
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
|
|
1814
|
-
const
|
|
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-
|
|
1947
|
-
children: /* @__PURE__ */ jsx(SparklesIcon, { className: "size-
|
|
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: [
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
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:
|
|
2137
|
+
from: "assistant",
|
|
1992
2138
|
children: /* @__PURE__ */ jsx(MessageContent, {
|
|
1993
|
-
className:
|
|
1994
|
-
children:
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
2278
|
-
setThreads(
|
|
2279
|
-
const current =
|
|
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
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
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: [
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
className:
|
|
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
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
event.
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
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
|
-
|
|
2430
|
-
|
|
2431
|
-
onClick: () => void
|
|
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(
|
|
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
|
-
}
|
|
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,
|