@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/README.md +315 -0
- package/dist/index.css +360 -0
- package/dist/index.d.mts +668 -0
- package/dist/index.d.ts +668 -0
- package/dist/index.js +1812 -0
- package/dist/index.mjs +1754 -0
- package/package.json +49 -0
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
|
+
};
|