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