@firstflow/react 0.0.1

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/dist/index.mjs ADDED
@@ -0,0 +1,1754 @@
1
+ // src/analytics/events.ts
2
+ var ISSUE_SUBMITTED = "issue_submitted";
3
+ var FEEDBACK_SUBMITTED = "feedback_submitted";
4
+ var SURVEY_COMPLETED = "survey_completed";
5
+ var EXPERIENCE_SHOWN = "experience_shown";
6
+ var EXPERIENCE_CLICKED = "experience_clicked";
7
+ var CHAT_MESSAGE_SENT = "chat_message_sent";
8
+
9
+ // src/analytics/identity.ts
10
+ var ANON_KEY = "ff_anon";
11
+ var SESSION_KEY = "ff_sess";
12
+ function uuid() {
13
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
14
+ const r = Math.random() * 16 | 0;
15
+ return (c === "x" ? r : r & 3 | 8).toString(16);
16
+ });
17
+ }
18
+ function getStorage(key, storage) {
19
+ if (typeof window === "undefined" || !storage) return uuid();
20
+ try {
21
+ let value = storage.getItem(key);
22
+ if (!value) {
23
+ value = uuid();
24
+ storage.setItem(key, value);
25
+ }
26
+ return value;
27
+ } catch {
28
+ return uuid();
29
+ }
30
+ }
31
+ function getAnonymousId() {
32
+ return getStorage(ANON_KEY, typeof window !== "undefined" ? window.localStorage : null);
33
+ }
34
+ function getSessionId() {
35
+ return getStorage(SESSION_KEY, typeof window !== "undefined" ? window.sessionStorage : null);
36
+ }
37
+
38
+ // src/analytics/device.ts
39
+ function getDeviceType() {
40
+ if (typeof window === "undefined") return "desktop";
41
+ const w = window.innerWidth;
42
+ if (w <= 768) return "mobile";
43
+ if (w <= 1024) return "tablet";
44
+ return "desktop";
45
+ }
46
+
47
+ // src/analytics/jitsu.ts
48
+ var DEFAULT_JITSU_HOST = "https://cmmiyu46c00003b6unusxjf0j.analytics.firstflow.dev";
49
+ var DEFAULT_JITSU_KEY = "P5qcd1d4Bd98EdxS4EfnucLohE2Ch37K:U4OECTXofJVzR5jhjaCpaYj478871PFU";
50
+ function uuid2() {
51
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
52
+ const r = Math.random() * 16 | 0;
53
+ return (c === "x" ? r : r & 3 | 8).toString(16);
54
+ });
55
+ }
56
+ function sendTrack(config, event, properties = {}) {
57
+ const host = config.jitsuHost || DEFAULT_JITSU_HOST;
58
+ const key = config.jitsuWriteKey || DEFAULT_JITSU_KEY;
59
+ const payload = {
60
+ messageId: uuid2(),
61
+ type: "track",
62
+ event,
63
+ anonymousId: getAnonymousId(),
64
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
65
+ properties: {
66
+ agent_id: config.agentId,
67
+ session_id: getSessionId(),
68
+ device_type: getDeviceType(),
69
+ ...properties
70
+ }
71
+ };
72
+ const auth = "Basic " + btoa(key);
73
+ fetch(`${host.replace(/\/$/, "")}/api/s/track`, {
74
+ method: "POST",
75
+ headers: {
76
+ "Content-Type": "application/json",
77
+ Authorization: auth
78
+ },
79
+ body: JSON.stringify(payload),
80
+ keepalive: true
81
+ }).catch(() => {
82
+ });
83
+ }
84
+
85
+ // src/analytics/analytics.ts
86
+ var DEFAULT_JITSU_HOST2 = "https://cmmiyu46c00003b6unusxjf0j.analytics.firstflow.dev";
87
+ var DEFAULT_JITSU_KEY2 = "P5qcd1d4Bd98EdxS4EfnucLohE2Ch37K:U4OECTXofJVzR5jhjaCpaYj478871PFU";
88
+ function createAnalytics(config) {
89
+ const agentId = config.agentId;
90
+ const jitsuHost = config.jitsuHost ?? DEFAULT_JITSU_HOST2;
91
+ const jitsuWriteKey = config.jitsuWriteKey ?? DEFAULT_JITSU_KEY2;
92
+ const transportConfig = { jitsuHost, jitsuWriteKey, agentId };
93
+ return {
94
+ track(eventName, properties = {}) {
95
+ sendTrack(transportConfig, eventName, properties);
96
+ },
97
+ identify(userId, traits = {}) {
98
+ if (userId) {
99
+ sendTrack(transportConfig, "user_identified", { user_id: userId, ...traits });
100
+ }
101
+ },
102
+ page(name, properties = {}) {
103
+ sendTrack(transportConfig, "page_view", { page_name: name ?? void 0, ...properties });
104
+ }
105
+ };
106
+ }
107
+
108
+ // src/config/defaults.ts
109
+ var DEFAULT_PROMPT_TEXT = "Something wrong? Report an issue and we'll look into it.";
110
+ var DEFAULT_FIELDS = [
111
+ { id: "name", label: "Name", type: "text", required: true },
112
+ { id: "email", label: "Email", type: "text", required: true },
113
+ { id: "subject", label: "Subject", type: "text", required: true },
114
+ { id: "description", label: "Description", type: "textarea", required: true },
115
+ { id: "session_id", label: "Session ID", type: "text", required: false }
116
+ ];
117
+
118
+ // src/config/normalizer.ts
119
+ function normalizeField(raw) {
120
+ const id = raw.id?.trim();
121
+ if (!id) return null;
122
+ if (raw.allowed === false) return null;
123
+ const type = raw.type === "textarea" || raw.type === "select" ? raw.type : "text";
124
+ return {
125
+ id,
126
+ type,
127
+ label: raw.label?.trim() ?? id,
128
+ required: raw.required === true,
129
+ options: type === "select" && Array.isArray(raw.options) ? raw.options : void 0
130
+ };
131
+ }
132
+ function normalizeConfig(agentId, apiUrl, raw) {
133
+ const enabled = raw?.enabled === true;
134
+ const promptText = typeof raw?.promptText === "string" && raw.promptText.trim() ? raw.promptText.trim() : DEFAULT_PROMPT_TEXT;
135
+ let fields = DEFAULT_FIELDS;
136
+ if (Array.isArray(raw?.fields) && raw.fields.length > 0) {
137
+ const normalized = raw.fields.map(normalizeField).filter((f) => f !== null);
138
+ if (normalized.length > 0) fields = normalized;
139
+ }
140
+ return {
141
+ agentId,
142
+ apiUrl: apiUrl.replace(/\/$/, ""),
143
+ enabled,
144
+ promptText,
145
+ fields
146
+ };
147
+ }
148
+
149
+ // src/validation/validate.ts
150
+ var CONTEXT_KEYS = /* @__PURE__ */ new Set([
151
+ "conversationId",
152
+ "messageId",
153
+ "sessionId",
154
+ "agentId",
155
+ "model"
156
+ ]);
157
+ function isContextKey(key) {
158
+ return CONTEXT_KEYS.has(key);
159
+ }
160
+ function validatePayload(data, fields) {
161
+ const errors = {};
162
+ const fieldIds = new Set(fields.map((f) => f.id));
163
+ for (const field of fields) {
164
+ const value = data[field.id];
165
+ const str = typeof value === "string" ? value.trim() : "";
166
+ if (field.required && !str) {
167
+ errors[field.id] = `${field.label ?? field.id} is required`;
168
+ continue;
169
+ }
170
+ if (!field.required && !str) continue;
171
+ if (field.type === "select" && field.options?.length) {
172
+ if (!field.options.includes(str)) {
173
+ errors[field.id] = `Please choose a valid option`;
174
+ }
175
+ }
176
+ }
177
+ return {
178
+ valid: Object.keys(errors).length === 0,
179
+ errors: Object.keys(errors).length > 0 ? errors : void 0
180
+ };
181
+ }
182
+ function getFormFieldsOnly(data, fields) {
183
+ const fieldIds = new Set(fields.map((f) => f.id));
184
+ const out = {};
185
+ for (const key of Object.keys(data)) {
186
+ if (isContextKey(key)) continue;
187
+ if (fieldIds.has(key)) out[key] = data[key];
188
+ }
189
+ return out;
190
+ }
191
+
192
+ // src/api/IssueReporter.ts
193
+ function extractContextMetadata(data, formFieldIds) {
194
+ const ctx = {};
195
+ for (const key of Object.keys(data)) {
196
+ if (formFieldIds.has(key)) continue;
197
+ const v = data[key];
198
+ if (v !== void 0) ctx[key] = v;
199
+ }
200
+ return ctx;
201
+ }
202
+ function buildPayload(formValues, contextMetadata) {
203
+ return { ...formValues, ...contextMetadata };
204
+ }
205
+ function createIssueReporter(options) {
206
+ const { agentId, apiUrl, config: rawConfig, analytics: analyticsOpt } = options;
207
+ const analytics = analyticsOpt ?? createAnalytics({ agentId });
208
+ let config = normalizeConfig(agentId, apiUrl, rawConfig);
209
+ let openHandler;
210
+ const instance = {
211
+ getConfig() {
212
+ return config;
213
+ },
214
+ setConfig(next) {
215
+ config = next;
216
+ },
217
+ async reportIssue(data) {
218
+ const formValues = getFormFieldsOnly(
219
+ data,
220
+ config.fields
221
+ );
222
+ const result = validatePayload(formValues, config.fields);
223
+ if (!result.valid) {
224
+ throw new Error(
225
+ result.errors ? Object.entries(result.errors).map(([k, v]) => `${k}: ${v}`).join(", ") : "Validation failed"
226
+ );
227
+ }
228
+ const formFieldIds = new Set(config.fields.map((f) => f.id));
229
+ const contextMetadata = extractContextMetadata(data, formFieldIds);
230
+ const payload = buildPayload(formValues, contextMetadata);
231
+ analytics.track(ISSUE_SUBMITTED, {
232
+ payload: JSON.stringify(payload),
233
+ metadata: JSON.stringify({})
234
+ });
235
+ },
236
+ open(options2) {
237
+ openHandler?.(options2);
238
+ },
239
+ setOpenHandler(handler) {
240
+ openHandler = handler;
241
+ },
242
+ destroy() {
243
+ openHandler = void 0;
244
+ }
245
+ };
246
+ return instance;
247
+ }
248
+
249
+ // src/config/feedbackNormalizer.ts
250
+ var DEFAULT_TAGS = ["Insightful", "Actionable", "Clear"];
251
+ var DEFAULT_HEADING = "Tell us more";
252
+ var DEFAULT_PLACEHOLDER = "Anything else you'd like to add? (optional)";
253
+ function normalizeSide(raw) {
254
+ const tags = Array.isArray(raw?.tags) && raw.tags.length > 0 ? [...new Set(raw.tags.map((t) => String(t).trim()).filter(Boolean))] : [...DEFAULT_TAGS];
255
+ return {
256
+ tags,
257
+ heading: typeof raw?.heading === "string" && raw.heading.trim() ? raw.heading.trim() : DEFAULT_HEADING,
258
+ placeholder: typeof raw?.placeholder === "string" && raw.placeholder.trim() ? raw.placeholder.trim() : DEFAULT_PLACEHOLDER,
259
+ showTags: raw?.showTags === true,
260
+ showComment: raw?.showComment === true
261
+ };
262
+ }
263
+ function normalizeFeedbackConfig(raw) {
264
+ const enabled = raw?.enabled === true;
265
+ return {
266
+ enabled,
267
+ like: normalizeSide(raw?.like),
268
+ dislike: normalizeSide(raw?.dislike)
269
+ };
270
+ }
271
+ function defaultFeedbackConfig() {
272
+ return normalizeFeedbackConfig({ enabled: false });
273
+ }
274
+
275
+ // src/api/MessageFeedback.ts
276
+ var METADATA_MAX_BYTES = 32768;
277
+ function normalizeMetadata(meta) {
278
+ if (!meta || typeof meta !== "object" || Array.isArray(meta)) return void 0;
279
+ if (Object.keys(meta).length === 0) return void 0;
280
+ try {
281
+ const s = JSON.stringify(meta);
282
+ if (s.length > METADATA_MAX_BYTES) {
283
+ console.warn(
284
+ `[Firstflow] feedback metadata omitted (serialized length ${s.length} > ${METADATA_MAX_BYTES})`
285
+ );
286
+ return void 0;
287
+ }
288
+ return JSON.parse(s);
289
+ } catch {
290
+ console.warn("[Firstflow] feedback metadata must be JSON-serializable; omitted");
291
+ return void 0;
292
+ }
293
+ }
294
+ function buildJitsuPayload(payload) {
295
+ const payloadObj = {
296
+ conversation_id: payload.conversationId.trim(),
297
+ message_id: payload.messageId.trim(),
298
+ rating: payload.rating,
299
+ submitted_at: (/* @__PURE__ */ new Date()).toISOString()
300
+ };
301
+ if (payload.comment != null && String(payload.comment).trim()) {
302
+ payloadObj.comment = String(payload.comment).trim();
303
+ }
304
+ if (payload.messagePreview != null && String(payload.messagePreview).trim()) {
305
+ payloadObj.message_preview = String(payload.messagePreview).trim().slice(0, 2e3);
306
+ }
307
+ if (payload.tags != null && payload.tags.length > 0) {
308
+ payloadObj.tags = payload.tags;
309
+ }
310
+ if (typeof payload.showTags === "boolean") {
311
+ payloadObj.showTags = payload.showTags;
312
+ }
313
+ if (typeof payload.showComment === "boolean") {
314
+ payloadObj.showComment = payload.showComment;
315
+ }
316
+ const metadata = normalizeMetadata(payload.metadata) ?? {};
317
+ return {
318
+ payload: JSON.stringify(payloadObj),
319
+ metadata: JSON.stringify(metadata),
320
+ rating: payload.rating
321
+ // top-level for ClickHouse summary queries
322
+ };
323
+ }
324
+ function createMessageFeedback(options) {
325
+ const { agentId, analytics: analyticsOpt, config: rawConfig } = options;
326
+ const analytics = analyticsOpt ?? createAnalytics({ agentId });
327
+ let config = normalizeFeedbackConfig(rawConfig ?? void 0);
328
+ return {
329
+ getConfig() {
330
+ return config;
331
+ },
332
+ setConfig(next) {
333
+ config = next;
334
+ },
335
+ isEnabled() {
336
+ return config.enabled === true;
337
+ },
338
+ async submit(payload) {
339
+ const cid = payload.conversationId?.trim();
340
+ const mid = payload.messageId?.trim();
341
+ if (!cid) throw new Error("conversationId is required");
342
+ if (!mid) throw new Error("messageId is required");
343
+ if (payload.rating !== "like" && payload.rating !== "dislike") {
344
+ throw new Error('rating must be "like" or "dislike"');
345
+ }
346
+ analytics.track(
347
+ FEEDBACK_SUBMITTED,
348
+ buildJitsuPayload({ ...payload, conversationId: cid, messageId: mid })
349
+ );
350
+ },
351
+ destroy() {
352
+ config = defaultFeedbackConfig();
353
+ }
354
+ };
355
+ }
356
+
357
+ // src/api/Firstflow.ts
358
+ var internalIssueMap = /* @__PURE__ */ new WeakMap();
359
+ var internalFeedbackMap = /* @__PURE__ */ new WeakMap();
360
+ function createIssueFacade(inner) {
361
+ return {
362
+ open(options) {
363
+ inner.open(options);
364
+ },
365
+ async submit(data) {
366
+ await inner.reportIssue(data);
367
+ },
368
+ getConfig() {
369
+ return inner.getConfig();
370
+ },
371
+ destroy() {
372
+ inner.destroy();
373
+ }
374
+ };
375
+ }
376
+ function createFeedbackFacade(inner) {
377
+ return {
378
+ submit(payload) {
379
+ return inner.submit(payload);
380
+ },
381
+ getConfig() {
382
+ return inner.getConfig();
383
+ },
384
+ isEnabled() {
385
+ return inner.isEnabled();
386
+ }
387
+ };
388
+ }
389
+ function getInternalIssueReporter(firstflow) {
390
+ const inner = internalIssueMap.get(firstflow);
391
+ if (!inner) throw new Error("Invalid Firstflow instance");
392
+ return inner;
393
+ }
394
+ function getInternalMessageFeedback(firstflow) {
395
+ const inner = internalFeedbackMap.get(firstflow);
396
+ if (!inner) throw new Error("Invalid Firstflow instance");
397
+ return inner;
398
+ }
399
+ function createFirstflow(options) {
400
+ const { agentId, apiUrl, jitsuHost, jitsuWriteKey, feedbackConfig } = options;
401
+ const analytics = createAnalytics({ agentId, jitsuHost, jitsuWriteKey });
402
+ const inner = createIssueReporter({ agentId, apiUrl, analytics });
403
+ const feedbackInner = createMessageFeedback({
404
+ agentId,
405
+ analytics,
406
+ config: feedbackConfig ?? void 0
407
+ });
408
+ const firstflow = {
409
+ get agentId() {
410
+ return agentId;
411
+ },
412
+ get apiUrl() {
413
+ return apiUrl;
414
+ },
415
+ analytics,
416
+ issue: createIssueFacade(inner),
417
+ feedback: createFeedbackFacade(feedbackInner)
418
+ };
419
+ internalIssueMap.set(firstflow, inner);
420
+ internalFeedbackMap.set(firstflow, feedbackInner);
421
+ return firstflow;
422
+ }
423
+
424
+ // src/context/FirstflowContext.tsx
425
+ import { createContext as createContext2, useContext as useContext2 } from "react";
426
+
427
+ // src/context/IssueReporterContext.tsx
428
+ import {
429
+ createContext,
430
+ useCallback as useCallback3,
431
+ useContext,
432
+ useEffect as useEffect3,
433
+ useRef,
434
+ useState as useState2
435
+ } from "react";
436
+
437
+ // src/components/IssueModal.tsx
438
+ import { useEffect as useEffect2 } from "react";
439
+ import { createPortal } from "react-dom";
440
+
441
+ // src/components/FormEngine.tsx
442
+ import { useCallback as useCallback2 } from "react";
443
+
444
+ // src/hooks/useIssueForm.ts
445
+ import { useCallback, useEffect, useState } from "react";
446
+ function emptyValues(fields) {
447
+ const init = {};
448
+ fields.forEach((f) => {
449
+ init[f.id] = "";
450
+ });
451
+ return init;
452
+ }
453
+ function useIssueForm() {
454
+ const { reporter, openOptionsRef } = useIssueReporterContext();
455
+ const config = reporter.getConfig();
456
+ const fields = config.fields;
457
+ const [values, setValuesState] = useState(() => emptyValues(fields));
458
+ const [errors, setErrors] = useState({});
459
+ const [submitting, setSubmitting] = useState(false);
460
+ const [submitted, setSubmitted] = useState(false);
461
+ const [submitError, setSubmitError] = useState(null);
462
+ const fieldsKey = fields.map((f) => f.id).join("\0");
463
+ useEffect(() => {
464
+ setValuesState(emptyValues(fields));
465
+ setErrors({});
466
+ setSubmitted(false);
467
+ setSubmitError(null);
468
+ }, [fieldsKey, reporter]);
469
+ const setValue = useCallback((fieldId, value) => {
470
+ setValuesState((prev) => ({ ...prev, [fieldId]: value }));
471
+ setErrors((prev) => prev[fieldId] ? { ...prev, [fieldId]: "" } : prev);
472
+ }, []);
473
+ const setValues = useCallback((next) => {
474
+ setValuesState(next);
475
+ }, []);
476
+ const validate = useCallback(() => {
477
+ const result = validatePayload(
478
+ values,
479
+ fields
480
+ );
481
+ if (!result.valid) {
482
+ setErrors(result.errors ?? {});
483
+ return false;
484
+ }
485
+ setErrors({});
486
+ return true;
487
+ }, [values, fields]);
488
+ const submit = useCallback(async () => {
489
+ if (!validate()) return;
490
+ setSubmitting(true);
491
+ setSubmitError(null);
492
+ const context = openOptionsRef.current ?? {};
493
+ try {
494
+ await reporter.reportIssue({ ...values, ...context });
495
+ setSubmitted(true);
496
+ } catch (e) {
497
+ const msg = e instanceof Error ? e.message : String(e);
498
+ setSubmitError(msg);
499
+ throw e;
500
+ } finally {
501
+ setSubmitting(false);
502
+ }
503
+ }, [validate, values, reporter, openOptionsRef]);
504
+ const reset = useCallback(() => {
505
+ setValuesState(emptyValues(fields));
506
+ setErrors({});
507
+ setSubmitted(false);
508
+ setSubmitError(null);
509
+ }, [fields]);
510
+ const clearSubmitError = useCallback(() => setSubmitError(null), []);
511
+ return {
512
+ values,
513
+ setValue,
514
+ setValues,
515
+ errors,
516
+ submitting,
517
+ submitted,
518
+ submitError,
519
+ validate,
520
+ submit,
521
+ reset,
522
+ clearSubmitError,
523
+ isEnabled: config.enabled,
524
+ config,
525
+ fields,
526
+ promptText: config.promptText
527
+ };
528
+ }
529
+
530
+ // src/components/FormEngine.module.css
531
+ var FormEngine_default = {};
532
+
533
+ // src/components/FormEngine.tsx
534
+ import { jsx, jsxs } from "react/jsx-runtime";
535
+ function FormEngine({
536
+ onSubmitSuccess,
537
+ onCancel,
538
+ submitLabel = "Submit",
539
+ cancelLabel = "Cancel"
540
+ }) {
541
+ const {
542
+ values,
543
+ setValue,
544
+ errors,
545
+ submitting,
546
+ submit,
547
+ submitError,
548
+ isEnabled,
549
+ fields,
550
+ promptText
551
+ } = useIssueForm();
552
+ const handleSubmit = useCallback2(
553
+ async (e) => {
554
+ e.preventDefault();
555
+ try {
556
+ await submit();
557
+ onSubmitSuccess?.();
558
+ } catch {
559
+ }
560
+ },
561
+ [submit, onSubmitSuccess]
562
+ );
563
+ if (!isEnabled) return null;
564
+ return /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: FormEngine_default.form, children: [
565
+ promptText ? /* @__PURE__ */ jsx("p", { className: FormEngine_default.prompt, children: promptText }) : null,
566
+ fields.map((field) => /* @__PURE__ */ jsxs("div", { className: FormEngine_default.field, children: [
567
+ /* @__PURE__ */ jsxs("label", { htmlFor: field.id, className: FormEngine_default.label, children: [
568
+ field.label,
569
+ field.required ? " *" : ""
570
+ ] }),
571
+ field.type === "textarea" ? /* @__PURE__ */ jsx(
572
+ "textarea",
573
+ {
574
+ id: field.id,
575
+ className: errors[field.id] ? `${FormEngine_default.textarea} ${FormEngine_default.textareaError}` : FormEngine_default.textarea,
576
+ value: values[field.id] ?? "",
577
+ onChange: (e) => setValue(field.id, e.target.value),
578
+ required: field.required
579
+ }
580
+ ) : field.type === "select" ? /* @__PURE__ */ jsxs(
581
+ "select",
582
+ {
583
+ id: field.id,
584
+ className: errors[field.id] ? `${FormEngine_default.select} ${FormEngine_default.selectError}` : FormEngine_default.select,
585
+ value: values[field.id] ?? "",
586
+ onChange: (e) => setValue(field.id, e.target.value),
587
+ required: field.required,
588
+ children: [
589
+ /* @__PURE__ */ jsx("option", { value: "", children: "Select\u2026" }),
590
+ (field.options ?? []).map((opt) => /* @__PURE__ */ jsx("option", { value: opt, children: opt }, opt))
591
+ ]
592
+ }
593
+ ) : /* @__PURE__ */ jsx(
594
+ "input",
595
+ {
596
+ id: field.id,
597
+ type: "text",
598
+ className: errors[field.id] ? `${FormEngine_default.input} ${FormEngine_default.inputError}` : FormEngine_default.input,
599
+ value: values[field.id] ?? "",
600
+ onChange: (e) => setValue(field.id, e.target.value),
601
+ required: field.required
602
+ }
603
+ ),
604
+ errors[field.id] ? /* @__PURE__ */ jsx("span", { className: FormEngine_default.errorText, children: errors[field.id] }) : null
605
+ ] }, field.id)),
606
+ submitError ? /* @__PURE__ */ jsx("p", { className: FormEngine_default.submitError, children: submitError }) : null,
607
+ /* @__PURE__ */ jsxs("div", { className: FormEngine_default.actions, children: [
608
+ onCancel ? /* @__PURE__ */ jsx("button", { type: "button", onClick: onCancel, disabled: submitting, children: cancelLabel }) : null,
609
+ /* @__PURE__ */ jsx("button", { type: "submit", disabled: submitting, children: submitting ? "Sending\u2026" : submitLabel })
610
+ ] })
611
+ ] });
612
+ }
613
+
614
+ // src/components/IssueModal.module.css
615
+ var IssueModal_default = {};
616
+
617
+ // src/components/IssueModal.tsx
618
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
619
+ function IssueModal() {
620
+ const { isModalOpen, closeModal } = useIssueReporterContext();
621
+ useEffect2(() => {
622
+ if (!isModalOpen) return;
623
+ const onKeyDown = (e) => {
624
+ if (e.key === "Escape") closeModal();
625
+ };
626
+ document.addEventListener("keydown", onKeyDown);
627
+ return () => document.removeEventListener("keydown", onKeyDown);
628
+ }, [isModalOpen, closeModal]);
629
+ if (!isModalOpen) return null;
630
+ const content = /* @__PURE__ */ jsx2(
631
+ "div",
632
+ {
633
+ className: IssueModal_default.overlay,
634
+ role: "dialog",
635
+ "aria-modal": "true",
636
+ "aria-labelledby": "ff-issue-modal-title",
637
+ onClick: (e) => e.target === e.currentTarget && closeModal(),
638
+ children: /* @__PURE__ */ jsxs2("div", { className: IssueModal_default.panel, onClick: (e) => e.stopPropagation(), children: [
639
+ /* @__PURE__ */ jsx2(
640
+ "button",
641
+ {
642
+ type: "button",
643
+ className: IssueModal_default.close,
644
+ onClick: closeModal,
645
+ "aria-label": "Close",
646
+ children: "\xD7"
647
+ }
648
+ ),
649
+ /* @__PURE__ */ jsx2("h2", { id: "ff-issue-modal-title", className: IssueModal_default.title, children: "Report an issue" }),
650
+ /* @__PURE__ */ jsx2(
651
+ FormEngine,
652
+ {
653
+ onSubmitSuccess: closeModal,
654
+ onCancel: closeModal,
655
+ submitLabel: "Submit",
656
+ cancelLabel: "Cancel"
657
+ }
658
+ )
659
+ ] })
660
+ }
661
+ );
662
+ return createPortal(content, document.body);
663
+ }
664
+
665
+ // src/context/IssueReporterContext.tsx
666
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
667
+ var IssueReporterContext = createContext(
668
+ null
669
+ );
670
+ function IssueReporterProvider({
671
+ reporter,
672
+ children
673
+ }) {
674
+ const [isModalOpen, setIsModalOpen] = useState2(false);
675
+ const openOptionsRef = useRef(void 0);
676
+ const openModal = useCallback3((options) => {
677
+ openOptionsRef.current = options;
678
+ setIsModalOpen(true);
679
+ }, []);
680
+ const closeModal = useCallback3(() => {
681
+ setIsModalOpen(false);
682
+ openOptionsRef.current = void 0;
683
+ }, []);
684
+ useEffect3(() => {
685
+ reporter.setOpenHandler(openModal);
686
+ return () => {
687
+ reporter.setOpenHandler(void 0);
688
+ };
689
+ }, [reporter, openModal]);
690
+ const value = {
691
+ reporter,
692
+ isModalOpen,
693
+ openModal,
694
+ closeModal,
695
+ openOptionsRef
696
+ };
697
+ return /* @__PURE__ */ jsxs3(IssueReporterContext.Provider, { value, children: [
698
+ children,
699
+ /* @__PURE__ */ jsx3(IssueModal, {})
700
+ ] });
701
+ }
702
+ function useIssueReporterContext() {
703
+ const ctx = useContext(IssueReporterContext);
704
+ if (ctx == null) {
705
+ throw new Error(
706
+ "useIssueReporterContext must be used within IssueReporterProvider"
707
+ );
708
+ }
709
+ return ctx;
710
+ }
711
+
712
+ // src/context/FirstflowContext.tsx
713
+ import { jsx as jsx4 } from "react/jsx-runtime";
714
+ var FirstflowContext = createContext2(null);
715
+ var FirstflowConfigContext = createContext2(null);
716
+ function FirstflowProvider({ firstflow, children, config = null }) {
717
+ const internalIssue = getInternalIssueReporter(firstflow);
718
+ return /* @__PURE__ */ jsx4(FirstflowConfigContext.Provider, { value: config ?? null, children: /* @__PURE__ */ jsx4(FirstflowContext.Provider, { value: firstflow, children: /* @__PURE__ */ jsx4(IssueReporterProvider, { reporter: internalIssue, children }) }) });
719
+ }
720
+ function useFirstflow() {
721
+ const ctx = useContext2(FirstflowContext);
722
+ if (ctx == null) {
723
+ throw new Error("useFirstflow must be used within FirstflowProvider");
724
+ }
725
+ return ctx;
726
+ }
727
+ function useFirstflowConfig() {
728
+ return useContext2(FirstflowConfigContext);
729
+ }
730
+
731
+ // src/hooks/useCreateFirstflow.ts
732
+ import { useEffect as useEffect4, useState as useState3 } from "react";
733
+
734
+ // src/config/fetcher.ts
735
+ function extractAgentRecord(json) {
736
+ if (!json || typeof json !== "object") return null;
737
+ const o = json;
738
+ const data = o.data;
739
+ if (data?.agent && typeof data.agent === "object") {
740
+ return data.agent;
741
+ }
742
+ if (o.agent && typeof o.agent === "object") {
743
+ return o.agent;
744
+ }
745
+ if ("issues_config" in o || "feedback_config" in o) {
746
+ return o;
747
+ }
748
+ return null;
749
+ }
750
+ function extractExperiencesFromResponse(json) {
751
+ if (!json || typeof json !== "object") return [];
752
+ const data = json.data;
753
+ const raw = data?.experiences;
754
+ if (!Array.isArray(raw)) return [];
755
+ return raw.filter((x) => x != null && typeof x === "object");
756
+ }
757
+ async function fetchSdkAgentConfig(apiUrl, agentId) {
758
+ const url = `${apiUrl.replace(/\/$/, "")}/agents/${agentId}/config`;
759
+ const res = await fetch(url);
760
+ if (!res.ok) throw new Error(`Failed to load config: ${res.status}`);
761
+ const json = await res.json();
762
+ const experiences = extractExperiencesFromResponse(json);
763
+ const agent = extractAgentRecord(json);
764
+ if (!agent) {
765
+ return experiences.length ? { experiences } : {};
766
+ }
767
+ return {
768
+ issues_config: agent.issues_config,
769
+ feedback_config: agent.feedback_config,
770
+ experiences
771
+ };
772
+ }
773
+
774
+ // src/hooks/useCreateFirstflow.ts
775
+ function useCreateFirstflow(agentId, apiUrl, options = {}) {
776
+ const { fetchConfig = false } = options;
777
+ const [firstflow, setFirstflow] = useState3(null);
778
+ const [loading, setLoading] = useState3(true);
779
+ const [error, setError] = useState3(null);
780
+ const [config, setConfig] = useState3(null);
781
+ useEffect4(() => {
782
+ if (!agentId || !apiUrl) {
783
+ setFirstflow(createFirstflow({ agentId: agentId || "", apiUrl: apiUrl || "" }));
784
+ setConfig(null);
785
+ setLoading(false);
786
+ return;
787
+ }
788
+ if (!fetchConfig) {
789
+ setFirstflow(createFirstflow({ agentId, apiUrl }));
790
+ setConfig(null);
791
+ setLoading(false);
792
+ return;
793
+ }
794
+ let cancelled = false;
795
+ setLoading(true);
796
+ setError(null);
797
+ setConfig(null);
798
+ (async () => {
799
+ try {
800
+ const sdk = await fetchSdkAgentConfig(apiUrl, agentId);
801
+ if (cancelled) return;
802
+ const inst = createFirstflow({ agentId, apiUrl });
803
+ getInternalIssueReporter(inst).setConfig(normalizeConfig(agentId, apiUrl, sdk.issues_config));
804
+ getInternalMessageFeedback(inst).setConfig(normalizeFeedbackConfig(sdk.feedback_config));
805
+ setFirstflow(inst);
806
+ setConfig(sdk);
807
+ } catch (e) {
808
+ if (!cancelled) {
809
+ setError(e instanceof Error ? e : new Error(String(e)));
810
+ setConfig(null);
811
+ }
812
+ } finally {
813
+ if (!cancelled) setLoading(false);
814
+ }
815
+ })();
816
+ return () => {
817
+ cancelled = true;
818
+ };
819
+ }, [agentId, apiUrl, fetchConfig]);
820
+ return { firstflow, loading, error, config };
821
+ }
822
+
823
+ // src/hooks/useFeedback.ts
824
+ import { useCallback as useCallback4, useEffect as useEffect5, useMemo, useRef as useRef2, useState as useState4 } from "react";
825
+ function storageKey(agentId, messageId) {
826
+ return `ff_feedback_${agentId}_${messageId}`;
827
+ }
828
+ function isMessageScope(o) {
829
+ return o != null && typeof o.conversationId === "string" && typeof o.messageId === "string" && typeof o.messagePreview === "string";
830
+ }
831
+ function useFeedback(options) {
832
+ const firstflow = useFirstflow();
833
+ const scoped = isMessageScope(options);
834
+ const [rating, setRatingState] = useState4(null);
835
+ const [selectedTags, setSelectedTags] = useState4([]);
836
+ const [comment, setCommentState] = useState4("");
837
+ const [submitting, setSubmitting] = useState4(false);
838
+ const [error, setError] = useState4(null);
839
+ const [submitted, setSubmitted] = useState4(false);
840
+ const isEnabled = firstflow.feedback.isEnabled();
841
+ const config = isEnabled ? firstflow.feedback.getConfig() : null;
842
+ const sideConfig = useMemo(() => {
843
+ if (!scoped || !config || !rating) return null;
844
+ return rating === "like" ? config.like : config.dislike;
845
+ }, [scoped, config, rating]);
846
+ const ratingRef = useRef2(rating);
847
+ ratingRef.current = rating;
848
+ const conversationId = scoped ? options.conversationId : "";
849
+ const messageId = scoped ? options.messageId : "";
850
+ const messagePreview = scoped ? options.messagePreview : "";
851
+ const metadata = scoped ? options.metadata : void 0;
852
+ useEffect5(() => {
853
+ if (!scoped) return;
854
+ setRatingState(null);
855
+ setSelectedTags([]);
856
+ setCommentState("");
857
+ setError(null);
858
+ setSubmitted(false);
859
+ if (!isEnabled) return;
860
+ try {
861
+ const raw = localStorage.getItem(storageKey(firstflow.agentId, messageId));
862
+ if (!raw) return;
863
+ const parsed = JSON.parse(raw);
864
+ if (parsed.rating === "like" || parsed.rating === "dislike") {
865
+ setRatingState(parsed.rating);
866
+ setSelectedTags(Array.isArray(parsed.tags) ? parsed.tags : []);
867
+ setCommentState(typeof parsed.comment === "string" ? parsed.comment : "");
868
+ }
869
+ } catch {
870
+ }
871
+ }, [scoped, firstflow.agentId, isEnabled, messageId]);
872
+ const setRating = useCallback4(
873
+ (r) => {
874
+ if (!scoped) return;
875
+ if (r !== ratingRef.current) {
876
+ setSelectedTags([]);
877
+ setCommentState("");
878
+ }
879
+ setRatingState(r);
880
+ setError(null);
881
+ setSubmitted(false);
882
+ },
883
+ [scoped]
884
+ );
885
+ const toggleTag = useCallback4(
886
+ (tag) => {
887
+ if (!scoped) return;
888
+ setSelectedTags((prev) => prev.includes(tag) ? prev.filter((t) => t !== tag) : [...prev, tag]);
889
+ setError(null);
890
+ },
891
+ [scoped]
892
+ );
893
+ const setComment = useCallback4(
894
+ (c) => {
895
+ if (!scoped) return;
896
+ setCommentState(c);
897
+ },
898
+ [scoped]
899
+ );
900
+ const clearError = useCallback4(() => setError(null), []);
901
+ const submit = useCallback4(async () => {
902
+ if (!scoped || !isEnabled || !rating) return;
903
+ setSubmitting(true);
904
+ setError(null);
905
+ const cfg = firstflow.feedback.getConfig();
906
+ const sideCfg = rating === "like" ? cfg.like : cfg.dislike;
907
+ const showTags = sideCfg.showTags === true;
908
+ const showComment = sideCfg.showComment === true;
909
+ const payload = {
910
+ conversationId,
911
+ messageId,
912
+ rating,
913
+ showTags,
914
+ showComment,
915
+ tags: showTags && selectedTags.length ? [...new Set(selectedTags)] : void 0,
916
+ comment: showComment && comment.trim() ? comment.trim() : void 0,
917
+ messagePreview: messagePreview.slice(0, 2e3),
918
+ metadata
919
+ };
920
+ try {
921
+ await firstflow.feedback.submit(payload);
922
+ try {
923
+ localStorage.setItem(
924
+ storageKey(firstflow.agentId, messageId),
925
+ JSON.stringify({ rating, tags: selectedTags, comment })
926
+ );
927
+ } catch {
928
+ }
929
+ setSubmitted(true);
930
+ } catch (e) {
931
+ setError(e instanceof Error ? e.message : "Failed to send feedback");
932
+ } finally {
933
+ setSubmitting(false);
934
+ }
935
+ }, [
936
+ scoped,
937
+ isEnabled,
938
+ rating,
939
+ conversationId,
940
+ messageId,
941
+ messagePreview,
942
+ metadata,
943
+ selectedTags,
944
+ comment,
945
+ firstflow,
946
+ isEnabled
947
+ ]);
948
+ if (!scoped) {
949
+ return firstflow.feedback;
950
+ }
951
+ return {
952
+ rating,
953
+ selectedTags,
954
+ comment,
955
+ submitting,
956
+ submitted,
957
+ error,
958
+ setRating,
959
+ toggleTag,
960
+ setComment,
961
+ submit,
962
+ clearError,
963
+ isEnabled,
964
+ config,
965
+ sideConfig
966
+ };
967
+ }
968
+
969
+ // src/hooks/useExperienceMessageNode.ts
970
+ import { useCallback as useCallback5, useEffect as useEffect6, useMemo as useMemo2, useRef as useRef3 } from "react";
971
+
972
+ // src/experience/flowMessageNode.ts
973
+ function normalizeFlowMessageStyle(style) {
974
+ if (style === "inline") return "message";
975
+ return style;
976
+ }
977
+ function normalizeFlowQuickReplyItem(entry) {
978
+ if (typeof entry === "string") return { label: entry };
979
+ if (entry && typeof entry === "object" && "label" in entry && typeof entry.label === "string") {
980
+ const o = entry;
981
+ return { label: o.label, prompt: o.prompt };
982
+ }
983
+ return { label: "" };
984
+ }
985
+ function normalizeFlowQuickReplyList(raw) {
986
+ if (!Array.isArray(raw)) return [];
987
+ return raw.map((x) => normalizeFlowQuickReplyItem(x));
988
+ }
989
+ var EXPERIENCE_MESSAGE_ANALYTICS_ACTION = {
990
+ cta_primary: "cta_primary",
991
+ cta_dismiss: "cta_dismiss",
992
+ quick_reply: "quick_reply",
993
+ carousel_cta: "carousel_cta"
994
+ };
995
+ var EXPERIENCE_MESSAGE_BLOCKS_VERSION = 4;
996
+ function buildExperienceMessageBlocks(config) {
997
+ const blocks = [];
998
+ const content = config.messageContent?.trim();
999
+ if (content) {
1000
+ blocks.push({ type: "text", body: content });
1001
+ }
1002
+ const style = config.messageStyle;
1003
+ if (style === "carousel" && config.carouselCards && config.carouselCards.length > 0) {
1004
+ blocks.push({ type: "carousel", cards: config.carouselCards });
1005
+ }
1006
+ if (style === "quick_replies") {
1007
+ const qr = normalizeFlowQuickReplyList(config.quickReplies).filter((q) => q.label.trim());
1008
+ if (qr.length > 0) {
1009
+ blocks.push({ type: "quick_replies", options: qr });
1010
+ }
1011
+ }
1012
+ const ctaType = config.ctaType ?? "none";
1013
+ const ctaText = config.ctaText?.trim();
1014
+ const skipCtaBlocks = style === "quick_replies";
1015
+ if (!skipCtaBlocks && (ctaType === "button" || ctaType === "link" || ctaType === "button_dismiss") && ctaText) {
1016
+ const promptTrim = config.ctaPrompt?.trim();
1017
+ blocks.push({
1018
+ type: "cta_primary",
1019
+ label: ctaText,
1020
+ ctaType,
1021
+ url: config.ctaUrl?.trim() || void 0,
1022
+ promptText: ctaType === "button" || ctaType === "button_dismiss" ? promptTrim || void 0 : void 0
1023
+ });
1024
+ }
1025
+ const dismissText = config.dismissText?.trim();
1026
+ if (!skipCtaBlocks && (ctaType === "dismiss" || ctaType === "button_dismiss") && dismissText) {
1027
+ blocks.push({ type: "cta_dismiss", label: dismissText });
1028
+ }
1029
+ return blocks;
1030
+ }
1031
+ function buildExperienceMessageUi(blocks, messageStyle) {
1032
+ return {
1033
+ hasText: blocks.some((b) => b.type === "text"),
1034
+ hasCarousel: blocks.some((b) => b.type === "carousel"),
1035
+ hasQuickReplies: blocks.some((b) => b.type === "quick_replies"),
1036
+ hasPrimaryCta: blocks.some((b) => b.type === "cta_primary"),
1037
+ hasDismissCta: blocks.some((b) => b.type === "cta_dismiss"),
1038
+ messageStyle: normalizeFlowMessageStyle(messageStyle)
1039
+ };
1040
+ }
1041
+ function buildExperienceCarouselCtaAction(config, cardId, actionId) {
1042
+ const id = actionId === "secondary" ? "secondary" : "primary";
1043
+ const card = config.carouselCards?.find((c) => c.id === cardId);
1044
+ if (!card) {
1045
+ return { kind: "carousel_cta", cardId, actionId: id };
1046
+ }
1047
+ const isSecondary = id === "secondary";
1048
+ const label = isSecondary ? card.secondaryButtonText?.trim() : card.buttonText?.trim();
1049
+ const buttonAction = isSecondary ? card.secondaryButtonAction ?? "prompt" : card.buttonAction ?? "prompt";
1050
+ const linkUrl = isSecondary ? card.secondaryButtonUrl?.trim() || void 0 : card.buttonUrl?.trim() || void 0;
1051
+ const promptText = isSecondary ? card.secondaryButtonPrompt?.trim() || void 0 : card.buttonPrompt?.trim() || void 0;
1052
+ return {
1053
+ kind: "carousel_cta",
1054
+ cardId,
1055
+ actionId: id,
1056
+ buttonAction,
1057
+ label,
1058
+ linkUrl,
1059
+ promptText
1060
+ };
1061
+ }
1062
+
1063
+ // src/hooks/useExperienceMessageNode.ts
1064
+ var shownImpressionKeys = /* @__PURE__ */ new Set();
1065
+ function buildClickAnalyticsBase(firstflowAgentId, experienceId, nodeId, conversationId, messageStyle, metadata, action) {
1066
+ const base = {
1067
+ ...metadata ?? {},
1068
+ agent_id: firstflowAgentId,
1069
+ experience_id: experienceId,
1070
+ node_id: nodeId,
1071
+ message_style: normalizeFlowMessageStyle(messageStyle) ?? null,
1072
+ action: EXPERIENCE_MESSAGE_ANALYTICS_ACTION[action.kind]
1073
+ };
1074
+ if (conversationId) base.conversation_id = conversationId;
1075
+ if (action.kind === "cta_primary") {
1076
+ base.cta_type = action.ctaType;
1077
+ base.cta_label = action.label;
1078
+ if (action.url) base.cta_url = action.url;
1079
+ if (action.promptText) base.cta_prompt = action.promptText;
1080
+ } else if (action.kind === "cta_dismiss") {
1081
+ base.dismiss_label = action.label;
1082
+ } else if (action.kind === "quick_reply") {
1083
+ base.quick_reply_label = action.label;
1084
+ base.quick_reply_index = action.index;
1085
+ if (action.promptText) base.quick_reply_prompt = action.promptText;
1086
+ } else if (action.kind === "carousel_cta") {
1087
+ base.card_id = action.cardId;
1088
+ base.carousel_action_id = action.actionId ?? "primary";
1089
+ if (action.buttonAction) base.carousel_button_action = action.buttonAction;
1090
+ if (action.label) base.carousel_button_label = action.label;
1091
+ if (action.linkUrl) base.carousel_link_url = action.linkUrl;
1092
+ if (action.promptText) base.carousel_prompt = action.promptText;
1093
+ }
1094
+ return base;
1095
+ }
1096
+ function buildShownAnalyticsBase(firstflowAgentId, experienceId, nodeId, conversationId, messageStyle, metadata) {
1097
+ return {
1098
+ ...metadata ?? {},
1099
+ agent_id: firstflowAgentId,
1100
+ experience_id: experienceId,
1101
+ node_id: nodeId,
1102
+ message_style: normalizeFlowMessageStyle(messageStyle) ?? null,
1103
+ ...conversationId ? { conversation_id: conversationId } : {}
1104
+ };
1105
+ }
1106
+ function useExperienceMessageNode(options) {
1107
+ const firstflow = useFirstflow();
1108
+ const {
1109
+ config,
1110
+ nodeId,
1111
+ experienceId,
1112
+ conversationId,
1113
+ metadata,
1114
+ trackShownOnMount = true,
1115
+ impressionKey: impressionKeyOpt,
1116
+ dedupeShown = true,
1117
+ onAction,
1118
+ mapAnalytics
1119
+ } = options;
1120
+ const blocks = useMemo2(() => buildExperienceMessageBlocks(config), [config]);
1121
+ const ui = useMemo2(
1122
+ () => buildExperienceMessageUi(blocks, config.messageStyle),
1123
+ [blocks, config.messageStyle]
1124
+ );
1125
+ const impressionKey = impressionKeyOpt ?? `${firstflow.agentId}\0${experienceId}\0${nodeId}\0${conversationId ?? ""}`;
1126
+ const reportShown = useCallback5(() => {
1127
+ if (dedupeShown && shownImpressionKeys.has(impressionKey)) {
1128
+ return;
1129
+ }
1130
+ const base = buildShownAnalyticsBase(
1131
+ firstflow.agentId,
1132
+ experienceId,
1133
+ nodeId,
1134
+ conversationId,
1135
+ config.messageStyle,
1136
+ metadata
1137
+ );
1138
+ let finalProps = base;
1139
+ try {
1140
+ finalProps = mapAnalytics?.(EXPERIENCE_SHOWN, { ...base }) ?? base;
1141
+ } catch (e) {
1142
+ console.warn("[Firstflow] useExperienceMessageNode mapAnalytics (experience_shown)", e);
1143
+ finalProps = base;
1144
+ }
1145
+ try {
1146
+ firstflow.analytics.track(EXPERIENCE_SHOWN, finalProps);
1147
+ if (dedupeShown) {
1148
+ shownImpressionKeys.add(impressionKey);
1149
+ }
1150
+ } catch (e) {
1151
+ console.warn("[Firstflow] useExperienceMessageNode analytics.track (experience_shown)", e);
1152
+ }
1153
+ }, [
1154
+ dedupeShown,
1155
+ impressionKey,
1156
+ firstflow,
1157
+ experienceId,
1158
+ nodeId,
1159
+ conversationId,
1160
+ config.messageStyle,
1161
+ metadata,
1162
+ mapAnalytics
1163
+ ]);
1164
+ const reportShownRef = useRef3(reportShown);
1165
+ reportShownRef.current = reportShown;
1166
+ useEffect6(() => {
1167
+ if (!trackShownOnMount) return;
1168
+ reportShownRef.current();
1169
+ }, [trackShownOnMount, impressionKey]);
1170
+ const emitClick = useCallback5(
1171
+ (action) => {
1172
+ try {
1173
+ onAction?.(action);
1174
+ } catch (e) {
1175
+ console.warn("[Firstflow] useExperienceMessageNode onAction", e);
1176
+ }
1177
+ const base = buildClickAnalyticsBase(
1178
+ firstflow.agentId,
1179
+ experienceId,
1180
+ nodeId,
1181
+ conversationId,
1182
+ config.messageStyle,
1183
+ metadata,
1184
+ action
1185
+ );
1186
+ let finalProps = base;
1187
+ try {
1188
+ finalProps = mapAnalytics?.(EXPERIENCE_CLICKED, { ...base }) ?? base;
1189
+ } catch (e) {
1190
+ console.warn("[Firstflow] useExperienceMessageNode mapAnalytics (experience_clicked)", e);
1191
+ finalProps = base;
1192
+ }
1193
+ try {
1194
+ firstflow.analytics.track(EXPERIENCE_CLICKED, finalProps);
1195
+ } catch (e) {
1196
+ console.warn("[Firstflow] useExperienceMessageNode analytics.track (experience_clicked)", e);
1197
+ }
1198
+ },
1199
+ [
1200
+ onAction,
1201
+ firstflow,
1202
+ experienceId,
1203
+ nodeId,
1204
+ conversationId,
1205
+ config.messageStyle,
1206
+ metadata,
1207
+ mapAnalytics
1208
+ ]
1209
+ );
1210
+ const handlers = useMemo2(() => {
1211
+ return {
1212
+ onPrimaryCta: () => {
1213
+ const b = blocks.find((x) => x.type === "cta_primary");
1214
+ if (!b) return;
1215
+ emitClick({
1216
+ kind: "cta_primary",
1217
+ ctaType: b.ctaType,
1218
+ label: b.label,
1219
+ url: b.url,
1220
+ promptText: b.promptText
1221
+ });
1222
+ },
1223
+ onDismissCta: () => {
1224
+ const b = blocks.find((x) => x.type === "cta_dismiss");
1225
+ if (!b) return;
1226
+ emitClick({ kind: "cta_dismiss", label: b.label });
1227
+ },
1228
+ onQuickReply: (label, index) => {
1229
+ const items = normalizeFlowQuickReplyList(config.quickReplies).filter((q) => q.label.trim());
1230
+ const item = items[index];
1231
+ emitClick({
1232
+ kind: "quick_reply",
1233
+ label: item?.label ?? label,
1234
+ index,
1235
+ promptText: item?.prompt?.trim() || void 0
1236
+ });
1237
+ },
1238
+ onCarouselCta: (cardId, actionId) => {
1239
+ emitClick(buildExperienceCarouselCtaAction(config, cardId, actionId));
1240
+ }
1241
+ };
1242
+ }, [blocks, config, emitClick]);
1243
+ return {
1244
+ config,
1245
+ blocks,
1246
+ blocksVersion: EXPERIENCE_MESSAGE_BLOCKS_VERSION,
1247
+ ui,
1248
+ handlers,
1249
+ reportShown
1250
+ };
1251
+ }
1252
+
1253
+ // src/hooks/useExperienceAnnouncementNode.ts
1254
+ import { useCallback as useCallback6, useEffect as useEffect7, useMemo as useMemo3, useRef as useRef4 } from "react";
1255
+
1256
+ // src/experience/flowAnnouncementNode.ts
1257
+ var EXPERIENCE_ANNOUNCEMENT_ANALYTICS_ACTION = {
1258
+ cta_click: "cta_click",
1259
+ dismiss: "dismiss",
1260
+ auto_hidden: "auto_hidden"
1261
+ };
1262
+ var EXPERIENCE_ANNOUNCEMENT_BLOCKS_VERSION = 1;
1263
+ var STYLES = /* @__PURE__ */ new Set([
1264
+ "chat_bubble",
1265
+ "card",
1266
+ "banner",
1267
+ "tooltip",
1268
+ "spotlight"
1269
+ ]);
1270
+ var THEMES = /* @__PURE__ */ new Set(["info", "success", "warning", "promo", "neutral"]);
1271
+ var POSITIONS = /* @__PURE__ */ new Set([
1272
+ "top",
1273
+ "bottom",
1274
+ "top-left",
1275
+ "top-right",
1276
+ "bottom-left",
1277
+ "bottom-right",
1278
+ "center"
1279
+ ]);
1280
+ function asStyle(v) {
1281
+ return typeof v === "string" && STYLES.has(v) ? v : void 0;
1282
+ }
1283
+ function asTheme(v) {
1284
+ return typeof v === "string" && THEMES.has(v) ? v : void 0;
1285
+ }
1286
+ function asPosition(v) {
1287
+ return typeof v === "string" && POSITIONS.has(v) ? v : void 0;
1288
+ }
1289
+ function toBool(v) {
1290
+ if (typeof v === "boolean") return v;
1291
+ return void 0;
1292
+ }
1293
+ function toPositiveInt(v) {
1294
+ if (typeof v === "number" && Number.isFinite(v) && v > 0) return Math.floor(v);
1295
+ if (typeof v === "string" && v.trim() !== "") {
1296
+ const n = parseInt(v, 10);
1297
+ if (Number.isFinite(n) && n > 0) return n;
1298
+ }
1299
+ return void 0;
1300
+ }
1301
+ function normalizeFlowAnnouncementNodeConfig(raw) {
1302
+ if (!raw || typeof raw !== "object") return {};
1303
+ const r = raw;
1304
+ return {
1305
+ announcementStyle: asStyle(r.announcementStyle),
1306
+ announcementTheme: asTheme(r.announcementTheme),
1307
+ announcementTitle: r.announcementTitle != null ? String(r.announcementTitle) : void 0,
1308
+ announcementBody: r.announcementBody != null ? String(r.announcementBody) : void 0,
1309
+ announcementEmoji: r.announcementEmoji != null ? String(r.announcementEmoji) : void 0,
1310
+ ctaText: r.ctaText != null ? String(r.ctaText) : void 0,
1311
+ ctaUrl: r.ctaUrl != null ? String(r.ctaUrl) : void 0,
1312
+ announcementDismissible: toBool(r.announcementDismissible),
1313
+ announcementAutoHide: toBool(r.announcementAutoHide),
1314
+ announcementAutoHideDelay: toPositiveInt(r.announcementAutoHideDelay),
1315
+ announcementPosition: asPosition(r.announcementPosition)
1316
+ };
1317
+ }
1318
+ function buildExperienceAnnouncementContent(config) {
1319
+ const content = {};
1320
+ const title = config.announcementTitle?.trim();
1321
+ if (title) content.title = title;
1322
+ const body = config.announcementBody?.trim();
1323
+ if (body) content.body = body;
1324
+ const emoji = config.announcementEmoji?.trim();
1325
+ if (emoji) content.emoji = emoji;
1326
+ const ctaLabel = config.ctaText?.trim();
1327
+ if (ctaLabel) {
1328
+ content.ctaLabel = ctaLabel;
1329
+ const url = config.ctaUrl?.trim();
1330
+ if (url) content.ctaUrl = url;
1331
+ }
1332
+ return content;
1333
+ }
1334
+ function buildExperienceAnnouncementUi(config, content) {
1335
+ const delay = config.announcementAutoHideDelay ?? 0;
1336
+ const autoHide = Boolean(config.announcementAutoHide && delay > 0);
1337
+ return {
1338
+ hasTitle: Boolean(content.title),
1339
+ hasBody: Boolean(content.body),
1340
+ hasEmoji: Boolean(content.emoji),
1341
+ hasCta: Boolean(content.ctaLabel),
1342
+ isDismissible: Boolean(config.announcementDismissible),
1343
+ autoHide,
1344
+ autoHideDelay: autoHide ? delay : 0,
1345
+ position: config.announcementPosition,
1346
+ style: config.announcementStyle,
1347
+ theme: config.announcementTheme
1348
+ };
1349
+ }
1350
+
1351
+ // src/hooks/useExperienceAnnouncementNode.ts
1352
+ var shownImpressionKeys2 = /* @__PURE__ */ new Set();
1353
+ function buildClickAnalyticsBase2(firstflowAgentId, experienceId, nodeId, conversationId, config, metadata, action) {
1354
+ const base = {
1355
+ ...metadata ?? {},
1356
+ agent_id: firstflowAgentId,
1357
+ experience_id: experienceId,
1358
+ node_id: nodeId,
1359
+ announcement_style: config.announcementStyle ?? null,
1360
+ announcement_theme: config.announcementTheme ?? null,
1361
+ action: EXPERIENCE_ANNOUNCEMENT_ANALYTICS_ACTION[action.kind]
1362
+ };
1363
+ if (conversationId) base.conversation_id = conversationId;
1364
+ if (action.kind === "cta_click") {
1365
+ base.cta_label = action.label;
1366
+ if (action.url) base.cta_url = action.url;
1367
+ }
1368
+ return base;
1369
+ }
1370
+ function buildShownAnalyticsBase2(firstflowAgentId, experienceId, nodeId, conversationId, config, metadata) {
1371
+ return {
1372
+ ...metadata ?? {},
1373
+ agent_id: firstflowAgentId,
1374
+ experience_id: experienceId,
1375
+ node_id: nodeId,
1376
+ announcement_style: config.announcementStyle ?? null,
1377
+ announcement_theme: config.announcementTheme ?? null,
1378
+ ...conversationId ? { conversation_id: conversationId } : {}
1379
+ };
1380
+ }
1381
+ function useExperienceAnnouncementNode(options) {
1382
+ const firstflow = useFirstflow();
1383
+ const {
1384
+ config: configInput,
1385
+ nodeId,
1386
+ experienceId,
1387
+ conversationId,
1388
+ metadata,
1389
+ trackShownOnMount = true,
1390
+ impressionKey: impressionKeyOpt,
1391
+ dedupeShown = true,
1392
+ onAction,
1393
+ mapAnalytics
1394
+ } = options;
1395
+ const config = useMemo3(
1396
+ () => normalizeFlowAnnouncementNodeConfig(configInput),
1397
+ [configInput]
1398
+ );
1399
+ const content = useMemo3(() => buildExperienceAnnouncementContent(config), [config]);
1400
+ const ui = useMemo3(() => buildExperienceAnnouncementUi(config, content), [config, content]);
1401
+ const impressionKey = impressionKeyOpt ?? `${firstflow.agentId}\0${experienceId}\0${nodeId}\0${conversationId ?? ""}`;
1402
+ const timerRef = useRef4(null);
1403
+ const clearAutoHideTimer = useCallback6(() => {
1404
+ if (timerRef.current != null) {
1405
+ clearTimeout(timerRef.current);
1406
+ timerRef.current = null;
1407
+ }
1408
+ }, []);
1409
+ const emitClick = useCallback6(
1410
+ (action) => {
1411
+ try {
1412
+ onAction?.(action);
1413
+ } catch (e) {
1414
+ console.warn("[Firstflow] useExperienceAnnouncementNode onAction", e);
1415
+ }
1416
+ const base = buildClickAnalyticsBase2(
1417
+ firstflow.agentId,
1418
+ experienceId,
1419
+ nodeId,
1420
+ conversationId,
1421
+ config,
1422
+ metadata,
1423
+ action
1424
+ );
1425
+ let finalProps = base;
1426
+ try {
1427
+ finalProps = mapAnalytics?.(EXPERIENCE_CLICKED, { ...base }) ?? base;
1428
+ } catch (e) {
1429
+ console.warn(
1430
+ "[Firstflow] useExperienceAnnouncementNode mapAnalytics (experience_clicked)",
1431
+ e
1432
+ );
1433
+ finalProps = base;
1434
+ }
1435
+ try {
1436
+ firstflow.analytics.track(EXPERIENCE_CLICKED, finalProps);
1437
+ } catch (e) {
1438
+ console.warn(
1439
+ "[Firstflow] useExperienceAnnouncementNode analytics.track (experience_clicked)",
1440
+ e
1441
+ );
1442
+ }
1443
+ },
1444
+ [onAction, firstflow, experienceId, nodeId, conversationId, config, metadata, mapAnalytics]
1445
+ );
1446
+ const emitClickRef = useRef4(emitClick);
1447
+ emitClickRef.current = emitClick;
1448
+ useEffect7(() => {
1449
+ if (!ui.autoHide || ui.autoHideDelay <= 0) return;
1450
+ timerRef.current = setTimeout(() => {
1451
+ timerRef.current = null;
1452
+ emitClickRef.current({ kind: "auto_hidden" });
1453
+ }, ui.autoHideDelay);
1454
+ return () => {
1455
+ if (timerRef.current != null) {
1456
+ clearTimeout(timerRef.current);
1457
+ timerRef.current = null;
1458
+ }
1459
+ };
1460
+ }, [ui.autoHide, ui.autoHideDelay, impressionKey]);
1461
+ const reportShown = useCallback6(() => {
1462
+ if (dedupeShown && shownImpressionKeys2.has(impressionKey)) {
1463
+ return;
1464
+ }
1465
+ const base = buildShownAnalyticsBase2(
1466
+ firstflow.agentId,
1467
+ experienceId,
1468
+ nodeId,
1469
+ conversationId,
1470
+ config,
1471
+ metadata
1472
+ );
1473
+ let finalProps = base;
1474
+ try {
1475
+ finalProps = mapAnalytics?.(EXPERIENCE_SHOWN, { ...base }) ?? base;
1476
+ } catch (e) {
1477
+ console.warn(
1478
+ "[Firstflow] useExperienceAnnouncementNode mapAnalytics (experience_shown)",
1479
+ e
1480
+ );
1481
+ finalProps = base;
1482
+ }
1483
+ try {
1484
+ firstflow.analytics.track(EXPERIENCE_SHOWN, finalProps);
1485
+ if (dedupeShown) {
1486
+ shownImpressionKeys2.add(impressionKey);
1487
+ }
1488
+ } catch (e) {
1489
+ console.warn(
1490
+ "[Firstflow] useExperienceAnnouncementNode analytics.track (experience_shown)",
1491
+ e
1492
+ );
1493
+ }
1494
+ }, [
1495
+ dedupeShown,
1496
+ impressionKey,
1497
+ firstflow,
1498
+ experienceId,
1499
+ nodeId,
1500
+ conversationId,
1501
+ config,
1502
+ metadata,
1503
+ mapAnalytics
1504
+ ]);
1505
+ const reportShownRef = useRef4(reportShown);
1506
+ reportShownRef.current = reportShown;
1507
+ useEffect7(() => {
1508
+ if (!trackShownOnMount) return;
1509
+ reportShownRef.current();
1510
+ }, [trackShownOnMount, impressionKey]);
1511
+ const dismiss = useCallback6(() => {
1512
+ clearAutoHideTimer();
1513
+ emitClick({ kind: "dismiss" });
1514
+ }, [clearAutoHideTimer, emitClick]);
1515
+ const ctaClick = useCallback6(() => {
1516
+ if (!content.ctaLabel) return;
1517
+ clearAutoHideTimer();
1518
+ emitClick({
1519
+ kind: "cta_click",
1520
+ label: content.ctaLabel,
1521
+ url: content.ctaUrl
1522
+ });
1523
+ }, [content.ctaLabel, content.ctaUrl, clearAutoHideTimer, emitClick]);
1524
+ const cancelAutoHide = useCallback6(() => {
1525
+ clearAutoHideTimer();
1526
+ }, [clearAutoHideTimer]);
1527
+ const handlers = useMemo3(
1528
+ () => ({
1529
+ dismiss,
1530
+ ctaClick,
1531
+ cancelAutoHide,
1532
+ reportShown
1533
+ }),
1534
+ [dismiss, ctaClick, cancelAutoHide, reportShown]
1535
+ );
1536
+ return {
1537
+ config,
1538
+ content,
1539
+ ui,
1540
+ blocksVersion: EXPERIENCE_ANNOUNCEMENT_BLOCKS_VERSION,
1541
+ handlers
1542
+ };
1543
+ }
1544
+
1545
+ // src/components/MessageFeedback.module.css
1546
+ var MessageFeedback_default = {};
1547
+
1548
+ // src/components/MessageFeedback.tsx
1549
+ import { Fragment, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1550
+ function ThumbsIcon({ up, active }) {
1551
+ if (up) {
1552
+ return /* @__PURE__ */ jsx5("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": true, children: /* @__PURE__ */ jsx5("path", { d: "M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3" }) });
1553
+ }
1554
+ return /* @__PURE__ */ jsx5("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": true, children: /* @__PURE__ */ jsx5("path", { d: "M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7.13-14h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.34 2H17" }) });
1555
+ }
1556
+ function MessageFeedback({
1557
+ conversationId,
1558
+ messageId,
1559
+ messagePreview,
1560
+ className,
1561
+ metadata,
1562
+ variant = "default",
1563
+ inlineFormSurface = "light"
1564
+ }) {
1565
+ const inline = variant === "inline";
1566
+ const {
1567
+ rating,
1568
+ selectedTags,
1569
+ comment,
1570
+ submitting,
1571
+ submitted,
1572
+ error,
1573
+ setRating,
1574
+ toggleTag,
1575
+ setComment,
1576
+ submit,
1577
+ isEnabled,
1578
+ sideConfig
1579
+ } = useFeedback({
1580
+ conversationId,
1581
+ messageId,
1582
+ messagePreview,
1583
+ metadata
1584
+ });
1585
+ if (!isEnabled) return null;
1586
+ const thumbs = /* @__PURE__ */ jsxs4("div", { className: MessageFeedback_default.thumbs, children: [
1587
+ /* @__PURE__ */ jsx5(
1588
+ "button",
1589
+ {
1590
+ type: "button",
1591
+ className: `${MessageFeedback_default.thumbBtn} ${MessageFeedback_default.thumbBtnLike} ${rating === "like" ? MessageFeedback_default.thumbBtnActiveLike : ""}`,
1592
+ onClick: () => setRating("like"),
1593
+ "aria-pressed": rating === "like",
1594
+ "aria-label": "Thumbs up",
1595
+ children: /* @__PURE__ */ jsx5(ThumbsIcon, { up: true, active: rating === "like" })
1596
+ }
1597
+ ),
1598
+ /* @__PURE__ */ jsx5(
1599
+ "button",
1600
+ {
1601
+ type: "button",
1602
+ className: `${MessageFeedback_default.thumbBtn} ${rating === "dislike" ? MessageFeedback_default.thumbBtnActiveDislike : ""}`,
1603
+ onClick: () => setRating("dislike"),
1604
+ "aria-pressed": rating === "dislike",
1605
+ "aria-label": "Thumbs down",
1606
+ children: /* @__PURE__ */ jsx5(ThumbsIcon, { up: false, active: rating === "dislike" })
1607
+ }
1608
+ )
1609
+ ] });
1610
+ const formBlock = rating && sideConfig ? /* @__PURE__ */ jsxs4(Fragment, { children: [
1611
+ /* @__PURE__ */ jsx5("p", { className: MessageFeedback_default.sectionHeading, children: sideConfig.heading.toUpperCase() }),
1612
+ sideConfig.showTags ? /* @__PURE__ */ jsx5("div", { className: MessageFeedback_default.tags, children: sideConfig.tags.map((tag) => /* @__PURE__ */ jsx5(
1613
+ "button",
1614
+ {
1615
+ type: "button",
1616
+ className: `${MessageFeedback_default.tag} ${selectedTags.includes(tag) ? MessageFeedback_default.tagSelected : ""}`,
1617
+ onClick: () => toggleTag(tag),
1618
+ children: tag
1619
+ },
1620
+ tag
1621
+ )) }) : null,
1622
+ sideConfig.showComment ? /* @__PURE__ */ jsx5(
1623
+ "textarea",
1624
+ {
1625
+ className: MessageFeedback_default.textarea,
1626
+ placeholder: sideConfig.placeholder,
1627
+ value: comment,
1628
+ onChange: (e) => setComment(e.target.value),
1629
+ "aria-label": "Optional comment"
1630
+ }
1631
+ ) : null,
1632
+ /* @__PURE__ */ jsx5("button", { type: "button", className: MessageFeedback_default.submitBtn, disabled: submitting, onClick: () => void submit(), children: submitting ? "Sending\u2026" : "Send feedback" }),
1633
+ submitted && !error && /* @__PURE__ */ jsx5("p", { className: MessageFeedback_default.hint, children: "Thanks \u2014 you can change your choice above and send again anytime." }),
1634
+ error && /* @__PURE__ */ jsx5("p", { className: MessageFeedback_default.error, children: error })
1635
+ ] }) : null;
1636
+ if (inline) {
1637
+ const thumbCardClass = [MessageFeedback_default.card, MessageFeedback_default.cardInline, className].filter(Boolean).join(" ");
1638
+ const formClass = [
1639
+ MessageFeedback_default.inlineFormBelow,
1640
+ inlineFormSurface === "dark" ? MessageFeedback_default.inlineFormBelowDark : ""
1641
+ ].filter(Boolean).join(" ");
1642
+ return /* @__PURE__ */ jsxs4(Fragment, { children: [
1643
+ /* @__PURE__ */ jsx5("div", { className: thumbCardClass, "data-ff-feedback-variant": "inline", "data-ff-inline-thumbs": "", children: thumbs }),
1644
+ formBlock && /* @__PURE__ */ jsx5("div", { className: formClass, "data-ff-inline-form": "", children: formBlock })
1645
+ ] });
1646
+ }
1647
+ const cardClass = [MessageFeedback_default.card, className].filter(Boolean).join(" ");
1648
+ return /* @__PURE__ */ jsxs4("div", { className: cardClass, "data-ff-feedback-variant": "default", children: [
1649
+ /* @__PURE__ */ jsx5("p", { className: MessageFeedback_default.previewLabel, children: "PREVIEW" }),
1650
+ /* @__PURE__ */ jsx5("div", { className: MessageFeedback_default.previewBox, children: messagePreview || "\u2014" }),
1651
+ thumbs,
1652
+ formBlock
1653
+ ] });
1654
+ }
1655
+
1656
+ // src/mount.tsx
1657
+ import { createRoot } from "react-dom/client";
1658
+
1659
+ // src/components/InlineIssueForm.module.css
1660
+ var InlineIssueForm_default = {};
1661
+
1662
+ // src/components/InlineIssueForm.tsx
1663
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1664
+ function InlineIssueForm() {
1665
+ const { reporter } = useIssueReporterContext();
1666
+ const config = reporter.getConfig();
1667
+ if (!config.enabled) return null;
1668
+ return /* @__PURE__ */ jsxs5("div", { className: InlineIssueForm_default.wrapper, children: [
1669
+ /* @__PURE__ */ jsx6("h3", { className: InlineIssueForm_default.title, children: "Report an issue" }),
1670
+ /* @__PURE__ */ jsx6(FormEngine, {})
1671
+ ] });
1672
+ }
1673
+
1674
+ // src/mount.tsx
1675
+ import { jsx as jsx7 } from "react/jsx-runtime";
1676
+ function mount(firstflow, target) {
1677
+ const root = createRoot(target);
1678
+ root.render(
1679
+ /* @__PURE__ */ jsx7(FirstflowProvider, { firstflow, children: /* @__PURE__ */ jsx7(InlineIssueForm, {}) })
1680
+ );
1681
+ return () => root.unmount();
1682
+ }
1683
+
1684
+ // src/hooks/useIssueReporter.ts
1685
+ import { useCallback as useCallback7 } from "react";
1686
+ function useIssueReporter() {
1687
+ const { reporter, isModalOpen, openModal, closeModal } = useIssueReporterContext();
1688
+ const open = useCallback7(
1689
+ (options) => {
1690
+ openModal(options);
1691
+ },
1692
+ [openModal]
1693
+ );
1694
+ const reportIssue = useCallback7(
1695
+ async (data) => {
1696
+ await reporter.reportIssue(data);
1697
+ },
1698
+ [reporter]
1699
+ );
1700
+ const submit = reportIssue;
1701
+ return {
1702
+ reporter,
1703
+ open,
1704
+ reportIssue,
1705
+ submit,
1706
+ config: reporter.getConfig(),
1707
+ isModalOpen,
1708
+ closeModal
1709
+ };
1710
+ }
1711
+
1712
+ // src/issue.ts
1713
+ var issue_exports = {};
1714
+ export {
1715
+ CHAT_MESSAGE_SENT,
1716
+ EXPERIENCE_ANNOUNCEMENT_ANALYTICS_ACTION,
1717
+ EXPERIENCE_ANNOUNCEMENT_BLOCKS_VERSION,
1718
+ EXPERIENCE_CLICKED,
1719
+ EXPERIENCE_MESSAGE_ANALYTICS_ACTION,
1720
+ EXPERIENCE_MESSAGE_BLOCKS_VERSION,
1721
+ EXPERIENCE_SHOWN,
1722
+ FEEDBACK_SUBMITTED,
1723
+ FirstflowProvider,
1724
+ FormEngine,
1725
+ ISSUE_SUBMITTED,
1726
+ InlineIssueForm,
1727
+ issue_exports as Issue,
1728
+ IssueModal,
1729
+ IssueReporterProvider,
1730
+ MessageFeedback,
1731
+ SURVEY_COMPLETED,
1732
+ buildExperienceAnnouncementContent,
1733
+ buildExperienceAnnouncementUi,
1734
+ buildExperienceCarouselCtaAction,
1735
+ buildExperienceMessageBlocks,
1736
+ buildExperienceMessageUi,
1737
+ createFirstflow,
1738
+ createIssueReporter,
1739
+ fetchSdkAgentConfig,
1740
+ mount,
1741
+ normalizeFlowAnnouncementNodeConfig,
1742
+ normalizeFlowMessageStyle,
1743
+ normalizeFlowQuickReplyItem,
1744
+ normalizeFlowQuickReplyList,
1745
+ useCreateFirstflow,
1746
+ useExperienceAnnouncementNode,
1747
+ useExperienceMessageNode,
1748
+ useFeedback,
1749
+ useFirstflow,
1750
+ useFirstflowConfig,
1751
+ useIssueForm,
1752
+ useIssueReporter,
1753
+ useIssueReporterContext
1754
+ };