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