@runtypelabs/persona 1.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +1080 -0
  2. package/dist/index.cjs +140 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +2626 -0
  5. package/dist/index.d.ts +2626 -0
  6. package/dist/index.global.js +1843 -0
  7. package/dist/index.global.js.map +1 -0
  8. package/dist/index.js +140 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/install.global.js +2 -0
  11. package/dist/install.global.js.map +1 -0
  12. package/dist/widget.css +1627 -0
  13. package/package.json +79 -0
  14. package/src/@types/idiomorph.d.ts +37 -0
  15. package/src/client.test.ts +387 -0
  16. package/src/client.ts +1589 -0
  17. package/src/components/composer-builder.ts +530 -0
  18. package/src/components/feedback.ts +379 -0
  19. package/src/components/forms.ts +170 -0
  20. package/src/components/header-builder.ts +455 -0
  21. package/src/components/header-layouts.ts +303 -0
  22. package/src/components/launcher.ts +193 -0
  23. package/src/components/message-bubble.ts +528 -0
  24. package/src/components/messages.ts +54 -0
  25. package/src/components/panel.ts +204 -0
  26. package/src/components/reasoning-bubble.ts +144 -0
  27. package/src/components/registry.ts +87 -0
  28. package/src/components/suggestions.ts +97 -0
  29. package/src/components/tool-bubble.ts +288 -0
  30. package/src/defaults.ts +321 -0
  31. package/src/index.ts +175 -0
  32. package/src/install.ts +284 -0
  33. package/src/plugins/registry.ts +77 -0
  34. package/src/plugins/types.ts +95 -0
  35. package/src/postprocessors.ts +194 -0
  36. package/src/runtime/init.ts +162 -0
  37. package/src/session.ts +376 -0
  38. package/src/styles/tailwind.css +20 -0
  39. package/src/styles/widget.css +1627 -0
  40. package/src/types.ts +1635 -0
  41. package/src/ui.ts +3341 -0
  42. package/src/utils/actions.ts +227 -0
  43. package/src/utils/attachment-manager.ts +384 -0
  44. package/src/utils/code-generators.test.ts +500 -0
  45. package/src/utils/code-generators.ts +1806 -0
  46. package/src/utils/component-middleware.ts +137 -0
  47. package/src/utils/component-parser.ts +119 -0
  48. package/src/utils/constants.ts +16 -0
  49. package/src/utils/content.ts +306 -0
  50. package/src/utils/dom.ts +25 -0
  51. package/src/utils/events.ts +41 -0
  52. package/src/utils/formatting.test.ts +166 -0
  53. package/src/utils/formatting.ts +470 -0
  54. package/src/utils/icons.ts +92 -0
  55. package/src/utils/message-id.ts +37 -0
  56. package/src/utils/morph.ts +36 -0
  57. package/src/utils/positioning.ts +17 -0
  58. package/src/utils/storage.ts +72 -0
  59. package/src/utils/theme.ts +105 -0
  60. package/src/widget.css +1 -0
  61. package/widget.css +1 -0
@@ -0,0 +1,379 @@
1
+ /**
2
+ * Feedback UI components for CSAT and NPS collection
3
+ */
4
+
5
+ export type CSATFeedbackOptions = {
6
+ /** Callback when user submits CSAT feedback */
7
+ onSubmit: (rating: number, comment?: string) => void | Promise<void>;
8
+ /** Callback when user dismisses the feedback form */
9
+ onDismiss?: () => void;
10
+ /** Title text */
11
+ title?: string;
12
+ /** Subtitle/question text */
13
+ subtitle?: string;
14
+ /** Placeholder for optional comment field */
15
+ commentPlaceholder?: string;
16
+ /** Submit button text */
17
+ submitText?: string;
18
+ /** Skip button text */
19
+ skipText?: string;
20
+ /** Show comment field */
21
+ showComment?: boolean;
22
+ /** Rating labels (5 items for ratings 1-5) */
23
+ ratingLabels?: [string, string, string, string, string];
24
+ };
25
+
26
+ export type NPSFeedbackOptions = {
27
+ /** Callback when user submits NPS feedback */
28
+ onSubmit: (rating: number, comment?: string) => void | Promise<void>;
29
+ /** Callback when user dismisses the feedback form */
30
+ onDismiss?: () => void;
31
+ /** Title text */
32
+ title?: string;
33
+ /** Subtitle/question text */
34
+ subtitle?: string;
35
+ /** Placeholder for optional comment field */
36
+ commentPlaceholder?: string;
37
+ /** Submit button text */
38
+ submitText?: string;
39
+ /** Skip button text */
40
+ skipText?: string;
41
+ /** Show comment field */
42
+ showComment?: boolean;
43
+ /** Low label (left side) */
44
+ lowLabel?: string;
45
+ /** High label (right side) */
46
+ highLabel?: string;
47
+ };
48
+
49
+ const defaultCSATLabels: [string, string, string, string, string] = [
50
+ 'Very dissatisfied',
51
+ 'Dissatisfied',
52
+ 'Neutral',
53
+ 'Satisfied',
54
+ 'Very satisfied'
55
+ ];
56
+
57
+ /**
58
+ * Create a CSAT (Customer Satisfaction) feedback form
59
+ * Rating scale: 1-5
60
+ */
61
+ export function createCSATFeedback(options: CSATFeedbackOptions): HTMLElement {
62
+ const {
63
+ onSubmit,
64
+ onDismiss,
65
+ title = 'How satisfied are you?',
66
+ subtitle = 'Please rate your experience',
67
+ commentPlaceholder = 'Share your thoughts (optional)...',
68
+ submitText = 'Submit',
69
+ skipText = 'Skip',
70
+ showComment = true,
71
+ ratingLabels = defaultCSATLabels,
72
+ } = options;
73
+
74
+ const container = document.createElement('div');
75
+ container.className = 'tvw-feedback-container tvw-feedback-csat';
76
+ container.setAttribute('role', 'dialog');
77
+ container.setAttribute('aria-label', 'Customer satisfaction feedback');
78
+
79
+ let selectedRating: number | null = null;
80
+
81
+ // Create inner content
82
+ const content = document.createElement('div');
83
+ content.className = 'tvw-feedback-content';
84
+
85
+ // Header
86
+ const header = document.createElement('div');
87
+ header.className = 'tvw-feedback-header';
88
+
89
+ const titleEl = document.createElement('h3');
90
+ titleEl.className = 'tvw-feedback-title';
91
+ titleEl.textContent = title;
92
+ header.appendChild(titleEl);
93
+
94
+ const subtitleEl = document.createElement('p');
95
+ subtitleEl.className = 'tvw-feedback-subtitle';
96
+ subtitleEl.textContent = subtitle;
97
+ header.appendChild(subtitleEl);
98
+
99
+ content.appendChild(header);
100
+
101
+ // Rating buttons (1-5 stars or numbers)
102
+ const ratingContainer = document.createElement('div');
103
+ ratingContainer.className = 'tvw-feedback-rating tvw-feedback-rating-csat';
104
+ ratingContainer.setAttribute('role', 'radiogroup');
105
+ ratingContainer.setAttribute('aria-label', 'Satisfaction rating from 1 to 5');
106
+
107
+ const ratingButtons: HTMLButtonElement[] = [];
108
+
109
+ for (let i = 1; i <= 5; i++) {
110
+ const ratingButton = document.createElement('button');
111
+ ratingButton.type = 'button';
112
+ ratingButton.className = 'tvw-feedback-rating-btn tvw-feedback-star-btn';
113
+ ratingButton.setAttribute('role', 'radio');
114
+ ratingButton.setAttribute('aria-checked', 'false');
115
+ ratingButton.setAttribute('aria-label', `${i} star${i > 1 ? 's' : ''}: ${ratingLabels[i - 1]}`);
116
+ ratingButton.title = ratingLabels[i - 1];
117
+ ratingButton.dataset.rating = String(i);
118
+
119
+ // Star icon (filled when selected)
120
+ ratingButton.innerHTML = `
121
+ <svg class="tvw-feedback-star" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
122
+ <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
123
+ </svg>
124
+ `;
125
+
126
+ ratingButton.addEventListener('click', () => {
127
+ selectedRating = i;
128
+ ratingButtons.forEach((btn, index) => {
129
+ const isSelected = index < i;
130
+ btn.classList.toggle('selected', isSelected);
131
+ btn.setAttribute('aria-checked', index === i - 1 ? 'true' : 'false');
132
+ });
133
+ });
134
+
135
+ ratingButtons.push(ratingButton);
136
+ ratingContainer.appendChild(ratingButton);
137
+ }
138
+
139
+ content.appendChild(ratingContainer);
140
+
141
+ // Comment field
142
+ let commentTextarea: HTMLTextAreaElement | null = null;
143
+ if (showComment) {
144
+ const commentContainer = document.createElement('div');
145
+ commentContainer.className = 'tvw-feedback-comment-container';
146
+
147
+ commentTextarea = document.createElement('textarea');
148
+ commentTextarea.className = 'tvw-feedback-comment';
149
+ commentTextarea.placeholder = commentPlaceholder;
150
+ commentTextarea.rows = 3;
151
+ commentTextarea.setAttribute('aria-label', 'Additional comments');
152
+
153
+ commentContainer.appendChild(commentTextarea);
154
+ content.appendChild(commentContainer);
155
+ }
156
+
157
+ // Action buttons
158
+ const actions = document.createElement('div');
159
+ actions.className = 'tvw-feedback-actions';
160
+
161
+ const skipButton = document.createElement('button');
162
+ skipButton.type = 'button';
163
+ skipButton.className = 'tvw-feedback-btn tvw-feedback-btn-skip';
164
+ skipButton.textContent = skipText;
165
+ skipButton.addEventListener('click', () => {
166
+ onDismiss?.();
167
+ container.remove();
168
+ });
169
+
170
+ const submitButton = document.createElement('button');
171
+ submitButton.type = 'button';
172
+ submitButton.className = 'tvw-feedback-btn tvw-feedback-btn-submit';
173
+ submitButton.textContent = submitText;
174
+ submitButton.addEventListener('click', async () => {
175
+ if (selectedRating === null) {
176
+ // Shake the rating container to indicate selection required
177
+ ratingContainer.classList.add('tvw-feedback-shake');
178
+ setTimeout(() => ratingContainer.classList.remove('tvw-feedback-shake'), 500);
179
+ return;
180
+ }
181
+
182
+ submitButton.disabled = true;
183
+ submitButton.textContent = 'Submitting...';
184
+
185
+ try {
186
+ const comment = commentTextarea?.value.trim() || undefined;
187
+ await onSubmit(selectedRating, comment);
188
+ container.remove();
189
+ } catch (error) {
190
+ submitButton.disabled = false;
191
+ submitButton.textContent = submitText;
192
+ // eslint-disable-next-line no-console
193
+ console.error('[CSAT Feedback] Failed to submit:', error);
194
+ }
195
+ });
196
+
197
+ actions.appendChild(skipButton);
198
+ actions.appendChild(submitButton);
199
+ content.appendChild(actions);
200
+
201
+ container.appendChild(content);
202
+
203
+ return container;
204
+ }
205
+
206
+ /**
207
+ * Create an NPS (Net Promoter Score) feedback form
208
+ * Rating scale: 0-10
209
+ */
210
+ export function createNPSFeedback(options: NPSFeedbackOptions): HTMLElement {
211
+ const {
212
+ onSubmit,
213
+ onDismiss,
214
+ title = 'How likely are you to recommend us?',
215
+ subtitle = 'On a scale of 0 to 10',
216
+ commentPlaceholder = 'What could we do better? (optional)...',
217
+ submitText = 'Submit',
218
+ skipText = 'Skip',
219
+ showComment = true,
220
+ lowLabel = 'Not likely',
221
+ highLabel = 'Very likely',
222
+ } = options;
223
+
224
+ const container = document.createElement('div');
225
+ container.className = 'tvw-feedback-container tvw-feedback-nps';
226
+ container.setAttribute('role', 'dialog');
227
+ container.setAttribute('aria-label', 'Net Promoter Score feedback');
228
+
229
+ let selectedRating: number | null = null;
230
+
231
+ // Create inner content
232
+ const content = document.createElement('div');
233
+ content.className = 'tvw-feedback-content';
234
+
235
+ // Header
236
+ const header = document.createElement('div');
237
+ header.className = 'tvw-feedback-header';
238
+
239
+ const titleEl = document.createElement('h3');
240
+ titleEl.className = 'tvw-feedback-title';
241
+ titleEl.textContent = title;
242
+ header.appendChild(titleEl);
243
+
244
+ const subtitleEl = document.createElement('p');
245
+ subtitleEl.className = 'tvw-feedback-subtitle';
246
+ subtitleEl.textContent = subtitle;
247
+ header.appendChild(subtitleEl);
248
+
249
+ content.appendChild(header);
250
+
251
+ // Rating buttons (0-10)
252
+ const ratingContainer = document.createElement('div');
253
+ ratingContainer.className = 'tvw-feedback-rating tvw-feedback-rating-nps';
254
+ ratingContainer.setAttribute('role', 'radiogroup');
255
+ ratingContainer.setAttribute('aria-label', 'Likelihood rating from 0 to 10');
256
+
257
+ // Labels row
258
+ const labelsRow = document.createElement('div');
259
+ labelsRow.className = 'tvw-feedback-labels';
260
+
261
+ const lowLabelEl = document.createElement('span');
262
+ lowLabelEl.className = 'tvw-feedback-label-low';
263
+ lowLabelEl.textContent = lowLabel;
264
+
265
+ const highLabelEl = document.createElement('span');
266
+ highLabelEl.className = 'tvw-feedback-label-high';
267
+ highLabelEl.textContent = highLabel;
268
+
269
+ labelsRow.appendChild(lowLabelEl);
270
+ labelsRow.appendChild(highLabelEl);
271
+
272
+ // Numbers row
273
+ const numbersRow = document.createElement('div');
274
+ numbersRow.className = 'tvw-feedback-numbers';
275
+
276
+ const ratingButtons: HTMLButtonElement[] = [];
277
+
278
+ for (let i = 0; i <= 10; i++) {
279
+ const ratingButton = document.createElement('button');
280
+ ratingButton.type = 'button';
281
+ ratingButton.className = 'tvw-feedback-rating-btn tvw-feedback-number-btn';
282
+ ratingButton.setAttribute('role', 'radio');
283
+ ratingButton.setAttribute('aria-checked', 'false');
284
+ ratingButton.setAttribute('aria-label', `Rating ${i} out of 10`);
285
+ ratingButton.textContent = String(i);
286
+ ratingButton.dataset.rating = String(i);
287
+
288
+ // Color coding: detractors (0-6), passives (7-8), promoters (9-10)
289
+ if (i <= 6) {
290
+ ratingButton.classList.add('tvw-feedback-detractor');
291
+ } else if (i <= 8) {
292
+ ratingButton.classList.add('tvw-feedback-passive');
293
+ } else {
294
+ ratingButton.classList.add('tvw-feedback-promoter');
295
+ }
296
+
297
+ ratingButton.addEventListener('click', () => {
298
+ selectedRating = i;
299
+ ratingButtons.forEach((btn, index) => {
300
+ btn.classList.toggle('selected', index === i);
301
+ btn.setAttribute('aria-checked', index === i ? 'true' : 'false');
302
+ });
303
+ });
304
+
305
+ ratingButtons.push(ratingButton);
306
+ numbersRow.appendChild(ratingButton);
307
+ }
308
+
309
+ ratingContainer.appendChild(labelsRow);
310
+ ratingContainer.appendChild(numbersRow);
311
+ content.appendChild(ratingContainer);
312
+
313
+ // Comment field
314
+ let commentTextarea: HTMLTextAreaElement | null = null;
315
+ if (showComment) {
316
+ const commentContainer = document.createElement('div');
317
+ commentContainer.className = 'tvw-feedback-comment-container';
318
+
319
+ commentTextarea = document.createElement('textarea');
320
+ commentTextarea.className = 'tvw-feedback-comment';
321
+ commentTextarea.placeholder = commentPlaceholder;
322
+ commentTextarea.rows = 3;
323
+ commentTextarea.setAttribute('aria-label', 'Additional comments');
324
+
325
+ commentContainer.appendChild(commentTextarea);
326
+ content.appendChild(commentContainer);
327
+ }
328
+
329
+ // Action buttons
330
+ const actions = document.createElement('div');
331
+ actions.className = 'tvw-feedback-actions';
332
+
333
+ const skipButton = document.createElement('button');
334
+ skipButton.type = 'button';
335
+ skipButton.className = 'tvw-feedback-btn tvw-feedback-btn-skip';
336
+ skipButton.textContent = skipText;
337
+ skipButton.addEventListener('click', () => {
338
+ onDismiss?.();
339
+ container.remove();
340
+ });
341
+
342
+ const submitButton = document.createElement('button');
343
+ submitButton.type = 'button';
344
+ submitButton.className = 'tvw-feedback-btn tvw-feedback-btn-submit';
345
+ submitButton.textContent = submitText;
346
+ submitButton.addEventListener('click', async () => {
347
+ if (selectedRating === null) {
348
+ // Shake the rating container to indicate selection required
349
+ numbersRow.classList.add('tvw-feedback-shake');
350
+ setTimeout(() => numbersRow.classList.remove('tvw-feedback-shake'), 500);
351
+ return;
352
+ }
353
+
354
+ submitButton.disabled = true;
355
+ submitButton.textContent = 'Submitting...';
356
+
357
+ try {
358
+ const comment = commentTextarea?.value.trim() || undefined;
359
+ await onSubmit(selectedRating, comment);
360
+ container.remove();
361
+ } catch (error) {
362
+ submitButton.disabled = false;
363
+ submitButton.textContent = submitText;
364
+ // eslint-disable-next-line no-console
365
+ console.error('[NPS Feedback] Failed to submit:', error);
366
+ }
367
+ });
368
+
369
+ actions.appendChild(skipButton);
370
+ actions.appendChild(submitButton);
371
+ content.appendChild(actions);
372
+
373
+ container.appendChild(content);
374
+
375
+ return container;
376
+ }
377
+
378
+
379
+
@@ -0,0 +1,170 @@
1
+ import { createElement } from "../utils/dom";
2
+ import { AgentWidgetMessage, AgentWidgetConfig } from "../types";
3
+ import { AgentWidgetSession } from "../session";
4
+
5
+ export const formDefinitions: Record<
6
+ string,
7
+ {
8
+ title: string;
9
+ description?: string;
10
+ fields: Array<{
11
+ name: string;
12
+ label: string;
13
+ placeholder?: string;
14
+ type?: "text" | "email" | "textarea";
15
+ required?: boolean;
16
+ }>;
17
+ submitLabel?: string;
18
+ }
19
+ > = {
20
+ init: {
21
+ title: "Schedule a Demo",
22
+ description: "Share the basics and we'll follow up with a confirmation.",
23
+ fields: [
24
+ { name: "name", label: "Full name", placeholder: "Jane Doe", required: true },
25
+ { name: "email", label: "Work email", placeholder: "jane@example.com", type: "email", required: true },
26
+ { name: "notes", label: "What would you like to cover?", type: "textarea" }
27
+ ],
28
+ submitLabel: "Submit details"
29
+ },
30
+ followup: {
31
+ title: "Additional Information",
32
+ description: "Provide any extra details to tailor the next steps.",
33
+ fields: [
34
+ { name: "company", label: "Company", placeholder: "Acme Inc." },
35
+ { name: "context", label: "Context", type: "textarea", placeholder: "Share more about your use case" }
36
+ ],
37
+ submitLabel: "Send"
38
+ }
39
+ };
40
+
41
+ export const enhanceWithForms = (
42
+ bubble: HTMLElement,
43
+ message: AgentWidgetMessage,
44
+ config: AgentWidgetConfig,
45
+ session: AgentWidgetSession
46
+ ) => {
47
+ const placeholders = bubble.querySelectorAll<HTMLElement>("[data-tv-form]");
48
+ if (placeholders.length) {
49
+ placeholders.forEach((placeholder) => {
50
+ if (placeholder.dataset.enhanced === "true") return;
51
+ const type = placeholder.dataset.tvForm ?? "init";
52
+ placeholder.dataset.enhanced = "true";
53
+
54
+ const definition = formDefinitions[type] ?? formDefinitions.init;
55
+ placeholder.classList.add("tvw-form-card", "tvw-space-y-4");
56
+
57
+ const heading = createElement("div", "tvw-space-y-1");
58
+ const title = createElement(
59
+ "h3",
60
+ "tvw-text-base tvw-font-semibold tvw-text-cw-primary"
61
+ );
62
+ title.textContent = definition.title;
63
+ heading.appendChild(title);
64
+ if (definition.description) {
65
+ const desc = createElement(
66
+ "p",
67
+ "tvw-text-sm tvw-text-cw-muted"
68
+ );
69
+ desc.textContent = definition.description;
70
+ heading.appendChild(desc);
71
+ }
72
+
73
+ const form = document.createElement("form");
74
+ form.className = "tvw-form-grid tvw-space-y-3";
75
+
76
+ definition.fields.forEach((field) => {
77
+ const group = createElement("label", "tvw-form-field tvw-flex tvw-flex-col tvw-gap-1");
78
+ group.htmlFor = `${message.id}-${type}-${field.name}`;
79
+ const label = createElement("span", "tvw-text-xs tvw-font-medium tvw-text-cw-muted");
80
+ label.textContent = field.label;
81
+ group.appendChild(label);
82
+
83
+ const inputType = field.type ?? "text";
84
+ let control: HTMLInputElement | HTMLTextAreaElement;
85
+ if (inputType === "textarea") {
86
+ control = document.createElement("textarea");
87
+ control.rows = 3;
88
+ } else {
89
+ control = document.createElement("input");
90
+ control.type = inputType;
91
+ }
92
+ control.className =
93
+ "tvw-rounded-xl tvw-border tvw-border-gray-200 tvw-bg-white tvw-px-3 tvw-py-2 tvw-text-sm tvw-text-cw-primary focus:tvw-outline-none focus:tvw-border-cw-primary";
94
+ control.id = `${message.id}-${type}-${field.name}`;
95
+ control.name = field.name;
96
+ control.placeholder = field.placeholder ?? "";
97
+ if (field.required) {
98
+ control.required = true;
99
+ }
100
+ group.appendChild(control);
101
+ form.appendChild(group);
102
+ });
103
+
104
+ const actions = createElement(
105
+ "div",
106
+ "tvw-flex tvw-items-center tvw-justify-between tvw-gap-2"
107
+ );
108
+ const status = createElement(
109
+ "div",
110
+ "tvw-text-xs tvw-text-cw-muted tvw-min-h-[1.5rem]"
111
+ );
112
+ const submit = createElement(
113
+ "button",
114
+ "tvw-inline-flex tvw-items-center tvw-rounded-full tvw-bg-cw-primary tvw-px-4 tvw-py-2 tvw-text-sm tvw-font-semibold tvw-text-white disabled:tvw-opacity-60 tvw-cursor-pointer"
115
+ ) as HTMLButtonElement;
116
+ submit.type = "submit";
117
+ submit.textContent = definition.submitLabel ?? "Submit";
118
+ actions.appendChild(status);
119
+ actions.appendChild(submit);
120
+ form.appendChild(actions);
121
+
122
+ placeholder.replaceChildren(heading, form);
123
+
124
+ form.addEventListener("submit", async (event) => {
125
+ event.preventDefault();
126
+ const formEndpoint = config.formEndpoint ?? "/form";
127
+ const formData = new FormData(form as HTMLFormElement);
128
+ const payload: Record<string, unknown> = {};
129
+ formData.forEach((value, key) => {
130
+ payload[key] = value;
131
+ });
132
+ payload["type"] = type;
133
+
134
+ submit.disabled = true;
135
+ status.textContent = "Submitting…";
136
+
137
+ try {
138
+ const response = await fetch(formEndpoint, {
139
+ method: "POST",
140
+ headers: {
141
+ "Content-Type": "application/json"
142
+ },
143
+ body: JSON.stringify(payload)
144
+ });
145
+ if (!response.ok) {
146
+ throw new Error(`Form submission failed (${response.status})`);
147
+ }
148
+ const data = await response.json();
149
+ status.textContent = data.message ?? "Thanks! We'll be in touch soon.";
150
+ if (data.success && data.nextPrompt) {
151
+ await session.sendMessage(String(data.nextPrompt));
152
+ }
153
+ } catch (error) {
154
+ status.textContent =
155
+ error instanceof Error ? error.message : "Something went wrong. Please try again.";
156
+ } finally {
157
+ submit.disabled = false;
158
+ }
159
+ });
160
+ });
161
+ }
162
+ };
163
+
164
+
165
+
166
+
167
+
168
+
169
+
170
+