@firstflow/react 0.0.1 → 0.0.101
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 +107 -315
- package/dist/chunk-WTTDFCFD.mjs +2157 -0
- package/dist/events-DAiGBrYc.d.mts +325 -0
- package/dist/events-DAiGBrYc.d.ts +325 -0
- package/dist/index.d.mts +356 -395
- package/dist/index.d.ts +356 -395
- package/dist/index.js +4726 -666
- package/dist/index.mjs +3242 -1272
- package/dist/internal.d.mts +131 -0
- package/dist/internal.d.ts +131 -0
- package/dist/internal.js +915 -0
- package/dist/internal.mjs +228 -0
- package/package.json +27 -9
- package/dist/index.css +0 -360
package/dist/internal.js
ADDED
|
@@ -0,0 +1,915 @@
|
|
|
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/internal.ts
|
|
21
|
+
var internal_exports = {};
|
|
22
|
+
__export(internal_exports, {
|
|
23
|
+
FEEDBACK_SUBMITTED: () => FEEDBACK_SUBMITTED,
|
|
24
|
+
ISSUE_SUBMITTED: () => ISSUE_SUBMITTED,
|
|
25
|
+
Issue: () => issue_exports,
|
|
26
|
+
IssueReporter: () => IssueReporter,
|
|
27
|
+
IssueReporterProvider: () => IssueReporterProvider,
|
|
28
|
+
createIssueReporter: () => createIssueReporter,
|
|
29
|
+
useFirstflowFeedback: () => useFirstflowFeedback,
|
|
30
|
+
useFirstflowIssueForm: () => useFirstflowIssueForm,
|
|
31
|
+
useFirstflowIssueReporter: () => useFirstflowIssueReporter,
|
|
32
|
+
useIssueReporterContext: () => useIssueReporterContext
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(internal_exports);
|
|
35
|
+
|
|
36
|
+
// src/analytics/events.ts
|
|
37
|
+
var ISSUE_SUBMITTED = "issue_submitted";
|
|
38
|
+
var FEEDBACK_SUBMITTED = "feedback_submitted";
|
|
39
|
+
|
|
40
|
+
// src/feedback/hooks/useFirstflowFeedback.ts
|
|
41
|
+
var import_react5 = require("react");
|
|
42
|
+
|
|
43
|
+
// src/platform/FirstflowContext.tsx
|
|
44
|
+
var import_react4 = require("react");
|
|
45
|
+
|
|
46
|
+
// src/analytics/identity.ts
|
|
47
|
+
var ANON_KEY = "__ff_anon_id";
|
|
48
|
+
var SESSION_KEY = "ff_sess";
|
|
49
|
+
function uuid() {
|
|
50
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
51
|
+
const r = Math.random() * 16 | 0;
|
|
52
|
+
return (c === "x" ? r : r & 3 | 8).toString(16);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function getStorage(key, storage) {
|
|
56
|
+
if (typeof window === "undefined" || !storage) return uuid();
|
|
57
|
+
try {
|
|
58
|
+
let value = storage.getItem(key);
|
|
59
|
+
if (!value) {
|
|
60
|
+
value = uuid();
|
|
61
|
+
storage.setItem(key, value);
|
|
62
|
+
}
|
|
63
|
+
return value;
|
|
64
|
+
} catch {
|
|
65
|
+
return uuid();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function getAnonymousId() {
|
|
69
|
+
return getStorage(ANON_KEY, typeof window !== "undefined" ? window.localStorage : null);
|
|
70
|
+
}
|
|
71
|
+
function getSessionId() {
|
|
72
|
+
return getStorage(SESSION_KEY, typeof window !== "undefined" ? window.sessionStorage : null);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/analytics/device.ts
|
|
76
|
+
function getDeviceType() {
|
|
77
|
+
if (typeof window === "undefined") return "desktop";
|
|
78
|
+
const w = window.innerWidth;
|
|
79
|
+
if (w <= 768) return "mobile";
|
|
80
|
+
if (w <= 1024) return "tablet";
|
|
81
|
+
return "desktop";
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/analytics/jitsu.ts
|
|
85
|
+
var DEFAULT_JITSU_HOST = "https://cmmiyu46c00003b6unusxjf0j.analytics.firstflow.dev";
|
|
86
|
+
var DEFAULT_JITSU_KEY = "P5qcd1d4Bd98EdxS4EfnucLohE2Ch37K:U4OECTXofJVzR5jhjaCpaYj478871PFU";
|
|
87
|
+
function uuid2() {
|
|
88
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
89
|
+
const r = Math.random() * 16 | 0;
|
|
90
|
+
return (c === "x" ? r : r & 3 | 8).toString(16);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
var RESERVED_KEYS = /* @__PURE__ */ new Set([
|
|
94
|
+
"anonymousId",
|
|
95
|
+
"agent_id",
|
|
96
|
+
"session_id",
|
|
97
|
+
"device_type",
|
|
98
|
+
"user_id",
|
|
99
|
+
"userId"
|
|
100
|
+
]);
|
|
101
|
+
function sanitizeTraits(traits) {
|
|
102
|
+
if (!traits) return {};
|
|
103
|
+
const out = {};
|
|
104
|
+
for (const [key, value] of Object.entries(traits)) {
|
|
105
|
+
if (RESERVED_KEYS.has(key)) continue;
|
|
106
|
+
out[key] = value;
|
|
107
|
+
}
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
function coerceTraitValueToString(value) {
|
|
111
|
+
if (value === null || value === void 0) return null;
|
|
112
|
+
if (typeof value === "string") return value;
|
|
113
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
114
|
+
if (typeof value === "boolean") return String(value);
|
|
115
|
+
try {
|
|
116
|
+
return JSON.stringify(value);
|
|
117
|
+
} catch {
|
|
118
|
+
return String(value);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function traitsRecordToStringMap(traits) {
|
|
122
|
+
const out = {};
|
|
123
|
+
for (const [k, v] of Object.entries(traits)) {
|
|
124
|
+
const s = coerceTraitValueToString(v);
|
|
125
|
+
if (s !== null) out[k] = s;
|
|
126
|
+
}
|
|
127
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
128
|
+
}
|
|
129
|
+
function buildTraitsStringMap(config) {
|
|
130
|
+
const merged = { ...sanitizeTraits(config.traits) };
|
|
131
|
+
const uid = config.userId?.trim();
|
|
132
|
+
if (uid) merged.user_id = uid;
|
|
133
|
+
return traitsRecordToStringMap(merged);
|
|
134
|
+
}
|
|
135
|
+
function buildJitsuTrackPayload(config, event, properties = {}) {
|
|
136
|
+
const traits = buildTraitsStringMap(config);
|
|
137
|
+
const payload = {
|
|
138
|
+
messageId: uuid2(),
|
|
139
|
+
type: "track",
|
|
140
|
+
event,
|
|
141
|
+
anonymousId: getAnonymousId(),
|
|
142
|
+
...config.userId ? { userId: config.userId } : {},
|
|
143
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
144
|
+
properties: {
|
|
145
|
+
agent_id: config.agentId,
|
|
146
|
+
session_id: getSessionId(),
|
|
147
|
+
device_type: getDeviceType(),
|
|
148
|
+
...properties
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
if (traits) payload.traits = traits;
|
|
152
|
+
return payload;
|
|
153
|
+
}
|
|
154
|
+
function sendTrack(config, event, properties = {}) {
|
|
155
|
+
const host = config.jitsuHost || DEFAULT_JITSU_HOST;
|
|
156
|
+
const key = config.jitsuWriteKey || DEFAULT_JITSU_KEY;
|
|
157
|
+
const payload = buildJitsuTrackPayload(config, event, properties);
|
|
158
|
+
const auth = "Basic " + btoa(key);
|
|
159
|
+
fetch(`${host.replace(/\/$/, "")}/api/s/track`, {
|
|
160
|
+
method: "POST",
|
|
161
|
+
headers: {
|
|
162
|
+
"Content-Type": "application/json",
|
|
163
|
+
Authorization: auth
|
|
164
|
+
},
|
|
165
|
+
body: JSON.stringify(payload),
|
|
166
|
+
keepalive: true
|
|
167
|
+
}).catch(() => {
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/analytics/analytics.ts
|
|
172
|
+
var DEFAULT_JITSU_HOST2 = "https://cmmiyu46c00003b6unusxjf0j.analytics.firstflow.dev";
|
|
173
|
+
var DEFAULT_JITSU_KEY2 = "P5qcd1d4Bd98EdxS4EfnucLohE2Ch37K:U4OECTXofJVzR5jhjaCpaYj478871PFU";
|
|
174
|
+
function createAnalytics(config) {
|
|
175
|
+
const agentId = config.agentId;
|
|
176
|
+
const jitsuHost = config.jitsuHost ?? DEFAULT_JITSU_HOST2;
|
|
177
|
+
const jitsuWriteKey = config.jitsuWriteKey ?? DEFAULT_JITSU_KEY2;
|
|
178
|
+
const identity = {
|
|
179
|
+
userId: config.userId,
|
|
180
|
+
traits: config.traits ?? {}
|
|
181
|
+
};
|
|
182
|
+
function transportConfig() {
|
|
183
|
+
return {
|
|
184
|
+
jitsuHost,
|
|
185
|
+
jitsuWriteKey,
|
|
186
|
+
agentId,
|
|
187
|
+
userId: identity.userId,
|
|
188
|
+
traits: identity.traits
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
track(eventName, properties = {}) {
|
|
193
|
+
sendTrack(transportConfig(), eventName, properties);
|
|
194
|
+
},
|
|
195
|
+
identify(userId, traits = {}) {
|
|
196
|
+
this.setUser(userId, traits);
|
|
197
|
+
if (!userId) return;
|
|
198
|
+
sendTrack(transportConfig(), "user_identified", {});
|
|
199
|
+
},
|
|
200
|
+
page(name, properties = {}) {
|
|
201
|
+
sendTrack(transportConfig(), "page_view", { page_name: name ?? void 0, ...properties });
|
|
202
|
+
},
|
|
203
|
+
setUser(userId, traits = {}) {
|
|
204
|
+
identity.userId = userId || void 0;
|
|
205
|
+
identity.traits = { ...traits ?? {} };
|
|
206
|
+
},
|
|
207
|
+
getUser() {
|
|
208
|
+
return {
|
|
209
|
+
userId: identity.userId,
|
|
210
|
+
traits: { ...identity.traits ?? {} }
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// src/issue/defaults.ts
|
|
217
|
+
var DEFAULT_PROMPT_TEXT = "Something wrong? Report an issue and we'll look into it.";
|
|
218
|
+
var DEFAULT_ISSUE_TITLE = "Report an issue";
|
|
219
|
+
var DEFAULT_ISSUE_TRIGGER_LABEL = "Report issue";
|
|
220
|
+
var DEFAULT_ISSUE_SUBMIT_LABEL = "Submit";
|
|
221
|
+
var DEFAULT_ISSUE_CANCEL_LABEL = "Cancel";
|
|
222
|
+
var DEFAULT_ISSUE_CANCEL_ENABLED = true;
|
|
223
|
+
var DEFAULT_FIELDS = [
|
|
224
|
+
{ id: "name", label: "Name", type: "text", required: true },
|
|
225
|
+
{ id: "email", label: "Email", type: "text", required: true },
|
|
226
|
+
{ id: "subject", label: "Subject", type: "text", required: true },
|
|
227
|
+
{ id: "description", label: "Description", type: "textarea", required: true },
|
|
228
|
+
{ id: "session_id", label: "Session ID", type: "text", required: false }
|
|
229
|
+
];
|
|
230
|
+
|
|
231
|
+
// src/issue/normalizer.ts
|
|
232
|
+
function normalizeField(raw) {
|
|
233
|
+
const id = raw.id?.trim();
|
|
234
|
+
if (!id) return null;
|
|
235
|
+
if (raw.allowed === false) return null;
|
|
236
|
+
const type = raw.type === "textarea" || raw.type === "select" ? raw.type : "text";
|
|
237
|
+
return {
|
|
238
|
+
id,
|
|
239
|
+
type,
|
|
240
|
+
label: raw.label?.trim() ?? id,
|
|
241
|
+
required: raw.required === true,
|
|
242
|
+
options: type === "select" && Array.isArray(raw.options) ? raw.options : void 0
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function normalizeConfig(agentId, apiUrl, raw) {
|
|
246
|
+
const enabled = raw?.enabled === true;
|
|
247
|
+
const title = typeof raw?.title === "string" && raw.title.trim() ? raw.title.trim() : DEFAULT_ISSUE_TITLE;
|
|
248
|
+
const triggerLabel = typeof raw?.triggerLabel === "string" && raw.triggerLabel.trim() ? raw.triggerLabel.trim() : DEFAULT_ISSUE_TRIGGER_LABEL;
|
|
249
|
+
const submitLabel = typeof raw?.submitLabel === "string" && raw.submitLabel.trim() ? raw.submitLabel.trim() : DEFAULT_ISSUE_SUBMIT_LABEL;
|
|
250
|
+
const cancelLabel = typeof raw?.cancelLabel === "string" && raw.cancelLabel.trim() ? raw.cancelLabel.trim() : DEFAULT_ISSUE_CANCEL_LABEL;
|
|
251
|
+
const cancelEnabled = typeof raw?.cancelEnabled === "boolean" ? raw.cancelEnabled : DEFAULT_ISSUE_CANCEL_ENABLED;
|
|
252
|
+
const promptText = typeof raw?.promptText === "string" && raw.promptText.trim() ? raw.promptText.trim() : DEFAULT_PROMPT_TEXT;
|
|
253
|
+
let fields = DEFAULT_FIELDS;
|
|
254
|
+
if (Array.isArray(raw?.fields) && raw.fields.length > 0) {
|
|
255
|
+
const normalized = raw.fields.map(normalizeField).filter((f) => f !== null);
|
|
256
|
+
if (normalized.length > 0) fields = normalized;
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
agentId,
|
|
260
|
+
apiUrl: apiUrl.replace(/\/$/, ""),
|
|
261
|
+
enabled,
|
|
262
|
+
title,
|
|
263
|
+
triggerLabel,
|
|
264
|
+
submitLabel,
|
|
265
|
+
cancelLabel,
|
|
266
|
+
cancelEnabled,
|
|
267
|
+
promptText,
|
|
268
|
+
fields
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// src/issue/validate.ts
|
|
273
|
+
var CONTEXT_KEYS = /* @__PURE__ */ new Set([
|
|
274
|
+
"conversationId",
|
|
275
|
+
"messageId",
|
|
276
|
+
"sessionId",
|
|
277
|
+
"agentId",
|
|
278
|
+
"model"
|
|
279
|
+
]);
|
|
280
|
+
function isContextKey(key) {
|
|
281
|
+
return CONTEXT_KEYS.has(key);
|
|
282
|
+
}
|
|
283
|
+
function validatePayload(data, fields) {
|
|
284
|
+
const errors = {};
|
|
285
|
+
const fieldIds = new Set(fields.map((f) => f.id));
|
|
286
|
+
for (const field of fields) {
|
|
287
|
+
const value = data[field.id];
|
|
288
|
+
const str = typeof value === "string" ? value.trim() : "";
|
|
289
|
+
if (field.required && !str) {
|
|
290
|
+
errors[field.id] = `${field.label ?? field.id} is required`;
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (!field.required && !str) continue;
|
|
294
|
+
if (field.type === "select" && field.options?.length) {
|
|
295
|
+
if (!field.options.includes(str)) {
|
|
296
|
+
errors[field.id] = `Please choose a valid option`;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return {
|
|
301
|
+
valid: Object.keys(errors).length === 0,
|
|
302
|
+
errors: Object.keys(errors).length > 0 ? errors : void 0
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
function getFormFieldsOnly(data, fields) {
|
|
306
|
+
const fieldIds = new Set(fields.map((f) => f.id));
|
|
307
|
+
const out = {};
|
|
308
|
+
for (const key of Object.keys(data)) {
|
|
309
|
+
if (isContextKey(key)) continue;
|
|
310
|
+
if (fieldIds.has(key)) out[key] = data[key];
|
|
311
|
+
}
|
|
312
|
+
return out;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// src/issue/IssueReporter.ts
|
|
316
|
+
function extractContextMetadata(data, formFieldIds) {
|
|
317
|
+
const ctx = {};
|
|
318
|
+
for (const key of Object.keys(data)) {
|
|
319
|
+
if (formFieldIds.has(key)) continue;
|
|
320
|
+
const v = data[key];
|
|
321
|
+
if (v !== void 0) ctx[key] = v;
|
|
322
|
+
}
|
|
323
|
+
return ctx;
|
|
324
|
+
}
|
|
325
|
+
function buildPayload(formValues, contextMetadata) {
|
|
326
|
+
return { ...formValues, ...contextMetadata };
|
|
327
|
+
}
|
|
328
|
+
function createIssueReporter(options) {
|
|
329
|
+
const { agentId, apiUrl, config: rawConfig, analytics: analyticsOpt } = options;
|
|
330
|
+
const analytics = analyticsOpt ?? createAnalytics({ agentId });
|
|
331
|
+
let config = normalizeConfig(agentId, apiUrl, rawConfig);
|
|
332
|
+
let openHandler;
|
|
333
|
+
const instance = {
|
|
334
|
+
getConfig() {
|
|
335
|
+
return config;
|
|
336
|
+
},
|
|
337
|
+
setConfig(next) {
|
|
338
|
+
config = next;
|
|
339
|
+
},
|
|
340
|
+
async reportIssue(data) {
|
|
341
|
+
const formValues = getFormFieldsOnly(
|
|
342
|
+
data,
|
|
343
|
+
config.fields
|
|
344
|
+
);
|
|
345
|
+
const result = validatePayload(formValues, config.fields);
|
|
346
|
+
if (!result.valid) {
|
|
347
|
+
throw new Error(
|
|
348
|
+
result.errors ? Object.entries(result.errors).map(([k, v]) => `${k}: ${v}`).join(", ") : "Validation failed"
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
const formFieldIds = new Set(config.fields.map((f) => f.id));
|
|
352
|
+
const contextMetadata = extractContextMetadata(data, formFieldIds);
|
|
353
|
+
const payload = buildPayload(formValues, contextMetadata);
|
|
354
|
+
analytics.track(ISSUE_SUBMITTED, {
|
|
355
|
+
payload: JSON.stringify(payload),
|
|
356
|
+
metadata: JSON.stringify({})
|
|
357
|
+
});
|
|
358
|
+
},
|
|
359
|
+
open(options2) {
|
|
360
|
+
openHandler?.(options2);
|
|
361
|
+
},
|
|
362
|
+
setOpenHandler(handler) {
|
|
363
|
+
openHandler = handler;
|
|
364
|
+
},
|
|
365
|
+
destroy() {
|
|
366
|
+
openHandler = void 0;
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
return instance;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// src/issue/IssueReporterContext.tsx
|
|
373
|
+
var import_react3 = require("react");
|
|
374
|
+
|
|
375
|
+
// src/components/IssueForm.tsx
|
|
376
|
+
var import_react2 = require("react");
|
|
377
|
+
|
|
378
|
+
// src/issue/hooks/useFirstflowIssueForm.ts
|
|
379
|
+
var import_react = require("react");
|
|
380
|
+
function emptyValues(fields) {
|
|
381
|
+
const init = {};
|
|
382
|
+
fields.forEach((f) => {
|
|
383
|
+
init[f.id] = "";
|
|
384
|
+
});
|
|
385
|
+
return init;
|
|
386
|
+
}
|
|
387
|
+
function useFirstflowIssueForm() {
|
|
388
|
+
const { reporter, openOptionsRef } = useIssueReporterContext();
|
|
389
|
+
const config = reporter.getConfig();
|
|
390
|
+
const fields = config.fields;
|
|
391
|
+
const [values, setValuesState] = (0, import_react.useState)(() => emptyValues(fields));
|
|
392
|
+
const [errors, setErrors] = (0, import_react.useState)({});
|
|
393
|
+
const [submitting, setSubmitting] = (0, import_react.useState)(false);
|
|
394
|
+
const [submitted, setSubmitted] = (0, import_react.useState)(false);
|
|
395
|
+
const [submitError, setSubmitError] = (0, import_react.useState)(null);
|
|
396
|
+
const fieldsKey = fields.map((f) => f.id).join("\0");
|
|
397
|
+
(0, import_react.useEffect)(() => {
|
|
398
|
+
setValuesState(emptyValues(fields));
|
|
399
|
+
setErrors({});
|
|
400
|
+
setSubmitted(false);
|
|
401
|
+
setSubmitError(null);
|
|
402
|
+
}, [fieldsKey, reporter]);
|
|
403
|
+
const setValue = (0, import_react.useCallback)((fieldId, value) => {
|
|
404
|
+
setValuesState((prev) => ({ ...prev, [fieldId]: value }));
|
|
405
|
+
setErrors((prev) => prev[fieldId] ? { ...prev, [fieldId]: "" } : prev);
|
|
406
|
+
}, []);
|
|
407
|
+
const setValues = (0, import_react.useCallback)((next) => {
|
|
408
|
+
setValuesState(next);
|
|
409
|
+
}, []);
|
|
410
|
+
const validate = (0, import_react.useCallback)(() => {
|
|
411
|
+
const result = validatePayload(
|
|
412
|
+
values,
|
|
413
|
+
fields
|
|
414
|
+
);
|
|
415
|
+
if (!result.valid) {
|
|
416
|
+
setErrors(result.errors ?? {});
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
setErrors({});
|
|
420
|
+
return true;
|
|
421
|
+
}, [values, fields]);
|
|
422
|
+
const submit = (0, import_react.useCallback)(async () => {
|
|
423
|
+
if (!validate()) return;
|
|
424
|
+
setSubmitting(true);
|
|
425
|
+
setSubmitError(null);
|
|
426
|
+
const context = openOptionsRef.current ?? {};
|
|
427
|
+
try {
|
|
428
|
+
await reporter.reportIssue({ ...values, ...context });
|
|
429
|
+
setSubmitted(true);
|
|
430
|
+
} catch (e) {
|
|
431
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
432
|
+
setSubmitError(msg);
|
|
433
|
+
throw e;
|
|
434
|
+
} finally {
|
|
435
|
+
setSubmitting(false);
|
|
436
|
+
}
|
|
437
|
+
}, [validate, values, reporter, openOptionsRef]);
|
|
438
|
+
const reset = (0, import_react.useCallback)(() => {
|
|
439
|
+
setValuesState(emptyValues(fields));
|
|
440
|
+
setErrors({});
|
|
441
|
+
setSubmitted(false);
|
|
442
|
+
setSubmitError(null);
|
|
443
|
+
}, [fields]);
|
|
444
|
+
const clearSubmitError = (0, import_react.useCallback)(() => setSubmitError(null), []);
|
|
445
|
+
return {
|
|
446
|
+
values,
|
|
447
|
+
setValue,
|
|
448
|
+
setValues,
|
|
449
|
+
errors,
|
|
450
|
+
submitting,
|
|
451
|
+
submitted,
|
|
452
|
+
submitError,
|
|
453
|
+
validate,
|
|
454
|
+
submit,
|
|
455
|
+
reset,
|
|
456
|
+
clearSubmitError,
|
|
457
|
+
isEnabled: config.enabled,
|
|
458
|
+
config,
|
|
459
|
+
title: config.title,
|
|
460
|
+
triggerLabel: config.triggerLabel,
|
|
461
|
+
submitLabel: config.submitLabel,
|
|
462
|
+
cancelLabel: config.cancelLabel,
|
|
463
|
+
cancelEnabled: config.cancelEnabled,
|
|
464
|
+
uiText: {
|
|
465
|
+
title: config.title,
|
|
466
|
+
triggerLabel: config.triggerLabel,
|
|
467
|
+
submitLabel: config.submitLabel,
|
|
468
|
+
cancelLabel: config.cancelLabel,
|
|
469
|
+
cancelEnabled: config.cancelEnabled,
|
|
470
|
+
promptText: config.promptText
|
|
471
|
+
},
|
|
472
|
+
fields,
|
|
473
|
+
promptText: config.promptText
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// #style-inject:#style-inject
|
|
478
|
+
function styleInject(css, { insertAt } = {}) {
|
|
479
|
+
if (!css || typeof document === "undefined") return;
|
|
480
|
+
const head = document.head || document.getElementsByTagName("head")[0];
|
|
481
|
+
const style = document.createElement("style");
|
|
482
|
+
style.type = "text/css";
|
|
483
|
+
if (insertAt === "top") {
|
|
484
|
+
if (head.firstChild) {
|
|
485
|
+
head.insertBefore(style, head.firstChild);
|
|
486
|
+
} else {
|
|
487
|
+
head.appendChild(style);
|
|
488
|
+
}
|
|
489
|
+
} else {
|
|
490
|
+
head.appendChild(style);
|
|
491
|
+
}
|
|
492
|
+
if (style.styleSheet) {
|
|
493
|
+
style.styleSheet.cssText = css;
|
|
494
|
+
} else {
|
|
495
|
+
style.appendChild(document.createTextNode(css));
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// src/components/IssueForm.css
|
|
500
|
+
styleInject(".ffIssueBackdrop {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.5);\n display: grid;\n place-items: center;\n z-index: 1000;\n}\n.ffIssuePanel {\n width: min(560px, calc(100vw - 24px));\n border-radius: 14px;\n background: #fff;\n border: 1px solid #e6e6e6;\n padding: 16px;\n color: #121212;\n}\n.ffIssueTitle {\n margin: 0 0 8px;\n font-size: 18px;\n}\n.ffIssuePrompt {\n margin: 0 0 12px;\n font-size: 14px;\n color: #525252;\n}\n.ffIssueField {\n margin-bottom: 10px;\n}\n.ffIssueLabel {\n display: block;\n font-size: 12px;\n margin-bottom: 4px;\n color: #3f3f3f;\n}\n.ffIssueInput,\n.ffIssueTextarea,\n.ffIssueSelect {\n width: 100%;\n border: 1px solid #d4d4d4;\n border-radius: 8px;\n padding: 8px 10px;\n font-size: 14px;\n}\n.ffIssueTextarea {\n min-height: 84px;\n resize: vertical;\n}\n.ffIssueErrorText {\n margin-top: 4px;\n font-size: 12px;\n color: #b91c1c;\n}\n.ffIssueActions {\n margin-top: 12px;\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n}\n.ffIssueButton {\n border-radius: 8px;\n border: 1px solid #d4d4d4;\n padding: 8px 12px;\n background: #fff;\n color: #111;\n cursor: pointer;\n}\n.ffIssuePrimary {\n background: #111;\n color: #fff;\n border-color: #111;\n}\n.ffIssueStatusError {\n margin-top: 8px;\n font-size: 12px;\n color: #b91c1c;\n}\n.ffIssueStatusSuccess {\n margin-top: 8px;\n font-size: 12px;\n color: #166534;\n}\n");
|
|
501
|
+
|
|
502
|
+
// src/components/IssueForm.tsx
|
|
503
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
504
|
+
var styles = {
|
|
505
|
+
backdrop: "ffIssueBackdrop",
|
|
506
|
+
panel: "ffIssuePanel",
|
|
507
|
+
title: "ffIssueTitle",
|
|
508
|
+
prompt: "ffIssuePrompt",
|
|
509
|
+
field: "ffIssueField",
|
|
510
|
+
label: "ffIssueLabel",
|
|
511
|
+
input: "ffIssueInput",
|
|
512
|
+
textarea: "ffIssueTextarea",
|
|
513
|
+
select: "ffIssueSelect",
|
|
514
|
+
errorText: "ffIssueErrorText",
|
|
515
|
+
actions: "ffIssueActions",
|
|
516
|
+
button: "ffIssueButton",
|
|
517
|
+
primary: "ffIssuePrimary",
|
|
518
|
+
statusError: "ffIssueStatusError",
|
|
519
|
+
statusSuccess: "ffIssueStatusSuccess"
|
|
520
|
+
};
|
|
521
|
+
function renderField(field, value, error, onChange) {
|
|
522
|
+
if (field.type === "textarea") {
|
|
523
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
524
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
525
|
+
"textarea",
|
|
526
|
+
{
|
|
527
|
+
className: styles.textarea,
|
|
528
|
+
value,
|
|
529
|
+
onChange: (event) => onChange(field.id, event.target.value)
|
|
530
|
+
}
|
|
531
|
+
),
|
|
532
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: styles.errorText, children: error }) : null
|
|
533
|
+
] });
|
|
534
|
+
}
|
|
535
|
+
if (field.type === "select") {
|
|
536
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
537
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
538
|
+
"select",
|
|
539
|
+
{
|
|
540
|
+
className: styles.select,
|
|
541
|
+
value,
|
|
542
|
+
onChange: (event) => onChange(field.id, event.target.value),
|
|
543
|
+
children: [
|
|
544
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: "Select an option" }),
|
|
545
|
+
(field.options ?? []).map((option) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: option, children: option }, `${field.id}:${option}`))
|
|
546
|
+
]
|
|
547
|
+
}
|
|
548
|
+
),
|
|
549
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: styles.errorText, children: error }) : null
|
|
550
|
+
] });
|
|
551
|
+
}
|
|
552
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
553
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
554
|
+
"input",
|
|
555
|
+
{
|
|
556
|
+
className: styles.input,
|
|
557
|
+
value,
|
|
558
|
+
onChange: (event) => onChange(field.id, event.target.value)
|
|
559
|
+
}
|
|
560
|
+
),
|
|
561
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: styles.errorText, children: error }) : null
|
|
562
|
+
] });
|
|
563
|
+
}
|
|
564
|
+
function IssueReporter({ showTrigger = true }) {
|
|
565
|
+
const { isModalOpen, openModal, closeModal } = useIssueReporterContext();
|
|
566
|
+
const {
|
|
567
|
+
fields,
|
|
568
|
+
values,
|
|
569
|
+
errors,
|
|
570
|
+
setValue,
|
|
571
|
+
submit,
|
|
572
|
+
submitting,
|
|
573
|
+
submitted,
|
|
574
|
+
submitError,
|
|
575
|
+
clearSubmitError,
|
|
576
|
+
reset,
|
|
577
|
+
title,
|
|
578
|
+
promptText,
|
|
579
|
+
triggerLabel,
|
|
580
|
+
submitLabel,
|
|
581
|
+
cancelLabel,
|
|
582
|
+
cancelEnabled,
|
|
583
|
+
isEnabled
|
|
584
|
+
} = useFirstflowIssueForm();
|
|
585
|
+
(0, import_react2.useEffect)(() => {
|
|
586
|
+
if (!isModalOpen) {
|
|
587
|
+
reset();
|
|
588
|
+
clearSubmitError();
|
|
589
|
+
}
|
|
590
|
+
}, [isModalOpen, reset, clearSubmitError]);
|
|
591
|
+
if (!isEnabled) return null;
|
|
592
|
+
const handleSubmit = async (event) => {
|
|
593
|
+
event.preventDefault();
|
|
594
|
+
await submit();
|
|
595
|
+
};
|
|
596
|
+
const body = isModalOpen ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: styles.backdrop, role: "presentation", onClick: closeModal, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
597
|
+
"section",
|
|
598
|
+
{
|
|
599
|
+
className: styles.panel,
|
|
600
|
+
role: "dialog",
|
|
601
|
+
"aria-modal": "true",
|
|
602
|
+
"aria-label": title,
|
|
603
|
+
onClick: (event) => event.stopPropagation(),
|
|
604
|
+
children: [
|
|
605
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: styles.title, children: title }),
|
|
606
|
+
promptText ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: styles.prompt, children: promptText }) : null,
|
|
607
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { onSubmit: handleSubmit, children: [
|
|
608
|
+
fields.map((field) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: styles.field, children: [
|
|
609
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: styles.label, htmlFor: `ff-issue-${field.id}`, children: field.label }),
|
|
610
|
+
renderField(
|
|
611
|
+
field,
|
|
612
|
+
values[field.id] ?? "",
|
|
613
|
+
errors[field.id],
|
|
614
|
+
(fieldId, next) => setValue(fieldId, next)
|
|
615
|
+
)
|
|
616
|
+
] }, field.id)),
|
|
617
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: styles.actions, children: [
|
|
618
|
+
cancelEnabled ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: styles.button, onClick: closeModal, children: cancelLabel }) : null,
|
|
619
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
620
|
+
"button",
|
|
621
|
+
{
|
|
622
|
+
type: "submit",
|
|
623
|
+
className: `${styles.button} ${styles.primary}`,
|
|
624
|
+
disabled: submitting,
|
|
625
|
+
children: submitting ? "Submitting..." : submitLabel
|
|
626
|
+
}
|
|
627
|
+
)
|
|
628
|
+
] }),
|
|
629
|
+
submitError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: styles.statusError, children: submitError }) : null,
|
|
630
|
+
submitted ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: styles.statusSuccess, children: "Thanks for your report." }) : null
|
|
631
|
+
] })
|
|
632
|
+
]
|
|
633
|
+
}
|
|
634
|
+
) }) : null;
|
|
635
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
636
|
+
showTrigger ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: styles.button, onClick: () => openModal(), children: triggerLabel }) : null,
|
|
637
|
+
body
|
|
638
|
+
] });
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// src/issue/IssueReporterContext.tsx
|
|
642
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
643
|
+
var IssueReporterContext = (0, import_react3.createContext)(
|
|
644
|
+
null
|
|
645
|
+
);
|
|
646
|
+
function IssueReporterProvider({
|
|
647
|
+
reporter,
|
|
648
|
+
children
|
|
649
|
+
}) {
|
|
650
|
+
const [isModalOpen, setIsModalOpen] = (0, import_react3.useState)(false);
|
|
651
|
+
const openOptionsRef = (0, import_react3.useRef)(void 0);
|
|
652
|
+
const openModal = (0, import_react3.useCallback)((options) => {
|
|
653
|
+
openOptionsRef.current = options;
|
|
654
|
+
setIsModalOpen(true);
|
|
655
|
+
}, []);
|
|
656
|
+
const closeModal = (0, import_react3.useCallback)(() => {
|
|
657
|
+
setIsModalOpen(false);
|
|
658
|
+
openOptionsRef.current = void 0;
|
|
659
|
+
}, []);
|
|
660
|
+
(0, import_react3.useEffect)(() => {
|
|
661
|
+
reporter.setOpenHandler(openModal);
|
|
662
|
+
return () => {
|
|
663
|
+
reporter.setOpenHandler(void 0);
|
|
664
|
+
};
|
|
665
|
+
}, [reporter, openModal]);
|
|
666
|
+
const value = {
|
|
667
|
+
reporter,
|
|
668
|
+
isModalOpen,
|
|
669
|
+
openModal,
|
|
670
|
+
closeModal,
|
|
671
|
+
openOptionsRef
|
|
672
|
+
};
|
|
673
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(IssueReporterContext.Provider, { value, children: [
|
|
674
|
+
children,
|
|
675
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(IssueReporter, { showTrigger: false })
|
|
676
|
+
] });
|
|
677
|
+
}
|
|
678
|
+
function useIssueReporterContext() {
|
|
679
|
+
const ctx = (0, import_react3.useContext)(IssueReporterContext);
|
|
680
|
+
if (ctx == null) {
|
|
681
|
+
throw new Error(
|
|
682
|
+
"useIssueReporterContext must be used within IssueReporterProvider"
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
return ctx;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// src/platform/FirstflowContext.tsx
|
|
689
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
690
|
+
var FirstflowContext = (0, import_react4.createContext)(null);
|
|
691
|
+
function useFirstflow() {
|
|
692
|
+
const ctx = (0, import_react4.useContext)(FirstflowContext);
|
|
693
|
+
if (ctx == null) {
|
|
694
|
+
throw new Error("useFirstflow must be used within FirstflowProvider");
|
|
695
|
+
}
|
|
696
|
+
return ctx;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// src/feedback/hooks/useFirstflowFeedback.ts
|
|
700
|
+
function storageKey(agentId, messageId) {
|
|
701
|
+
return `ff_feedback_${agentId}_${messageId}`;
|
|
702
|
+
}
|
|
703
|
+
function isMessageScope(o) {
|
|
704
|
+
return o != null && typeof o.conversationId === "string" && typeof o.messageId === "string" && typeof o.messagePreview === "string";
|
|
705
|
+
}
|
|
706
|
+
function useFirstflowFeedback(options) {
|
|
707
|
+
const firstflow = useFirstflow();
|
|
708
|
+
const scoped = isMessageScope(options);
|
|
709
|
+
const [rating, setRatingState] = (0, import_react5.useState)(null);
|
|
710
|
+
const [selectedTags, setSelectedTags] = (0, import_react5.useState)([]);
|
|
711
|
+
const [comment, setCommentState] = (0, import_react5.useState)("");
|
|
712
|
+
const [submitting, setSubmitting] = (0, import_react5.useState)(false);
|
|
713
|
+
const [error, setError] = (0, import_react5.useState)(null);
|
|
714
|
+
const [submitted, setSubmitted] = (0, import_react5.useState)(false);
|
|
715
|
+
const isEnabled = firstflow.feedback.isEnabled();
|
|
716
|
+
const config = isEnabled ? firstflow.feedback.getConfig() : null;
|
|
717
|
+
const sideConfig = (0, import_react5.useMemo)(() => {
|
|
718
|
+
if (!scoped || !config || !rating) return null;
|
|
719
|
+
return rating === "like" ? config.like : config.dislike;
|
|
720
|
+
}, [scoped, config, rating]);
|
|
721
|
+
const ratingRef = (0, import_react5.useRef)(rating);
|
|
722
|
+
ratingRef.current = rating;
|
|
723
|
+
const conversationId = scoped ? options.conversationId : "";
|
|
724
|
+
const messageId = scoped ? options.messageId : "";
|
|
725
|
+
const messagePreview = scoped ? options.messagePreview : "";
|
|
726
|
+
const metadata = scoped ? options.metadata : void 0;
|
|
727
|
+
(0, import_react5.useEffect)(() => {
|
|
728
|
+
if (!scoped) return;
|
|
729
|
+
setRatingState(null);
|
|
730
|
+
setSelectedTags([]);
|
|
731
|
+
setCommentState("");
|
|
732
|
+
setError(null);
|
|
733
|
+
setSubmitted(false);
|
|
734
|
+
if (!isEnabled) return;
|
|
735
|
+
try {
|
|
736
|
+
const raw = localStorage.getItem(storageKey(firstflow.agentId, messageId));
|
|
737
|
+
if (!raw) return;
|
|
738
|
+
const parsed = JSON.parse(raw);
|
|
739
|
+
if (parsed.rating === "like" || parsed.rating === "dislike") {
|
|
740
|
+
setRatingState(parsed.rating);
|
|
741
|
+
setSelectedTags(Array.isArray(parsed.tags) ? parsed.tags : []);
|
|
742
|
+
setCommentState(typeof parsed.comment === "string" ? parsed.comment : "");
|
|
743
|
+
}
|
|
744
|
+
} catch {
|
|
745
|
+
}
|
|
746
|
+
}, [scoped, firstflow.agentId, isEnabled, messageId]);
|
|
747
|
+
const setRating = (0, import_react5.useCallback)(
|
|
748
|
+
(r) => {
|
|
749
|
+
if (!scoped) return;
|
|
750
|
+
if (r !== ratingRef.current) {
|
|
751
|
+
setSelectedTags([]);
|
|
752
|
+
setCommentState("");
|
|
753
|
+
}
|
|
754
|
+
setRatingState(r);
|
|
755
|
+
setError(null);
|
|
756
|
+
setSubmitted(false);
|
|
757
|
+
if (!isEnabled) return;
|
|
758
|
+
const cfg = firstflow.feedback.getConfig();
|
|
759
|
+
const sideCfg = r === "like" ? cfg.like : cfg.dislike;
|
|
760
|
+
if (sideCfg.showTags || sideCfg.showComment) return;
|
|
761
|
+
setSubmitting(true);
|
|
762
|
+
const autoPayload = {
|
|
763
|
+
conversationId,
|
|
764
|
+
messageId,
|
|
765
|
+
rating: r,
|
|
766
|
+
showTags: false,
|
|
767
|
+
showComment: false,
|
|
768
|
+
messagePreview: messagePreview.slice(0, 2e3),
|
|
769
|
+
metadata
|
|
770
|
+
};
|
|
771
|
+
void firstflow.feedback.submit(autoPayload).then(() => {
|
|
772
|
+
try {
|
|
773
|
+
localStorage.setItem(
|
|
774
|
+
storageKey(firstflow.agentId, messageId),
|
|
775
|
+
JSON.stringify({ rating: r, tags: [], comment: "" })
|
|
776
|
+
);
|
|
777
|
+
} catch {
|
|
778
|
+
}
|
|
779
|
+
setSubmitted(true);
|
|
780
|
+
}).catch((e) => {
|
|
781
|
+
setError(e instanceof Error ? e.message : "Failed to send feedback");
|
|
782
|
+
}).finally(() => {
|
|
783
|
+
setSubmitting(false);
|
|
784
|
+
});
|
|
785
|
+
},
|
|
786
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
787
|
+
[scoped, isEnabled, firstflow, conversationId, messageId, messagePreview, metadata]
|
|
788
|
+
);
|
|
789
|
+
const toggleTag = (0, import_react5.useCallback)(
|
|
790
|
+
(tag) => {
|
|
791
|
+
if (!scoped) return;
|
|
792
|
+
setSelectedTags((prev) => prev.includes(tag) ? prev.filter((t) => t !== tag) : [...prev, tag]);
|
|
793
|
+
setError(null);
|
|
794
|
+
},
|
|
795
|
+
[scoped]
|
|
796
|
+
);
|
|
797
|
+
const setComment = (0, import_react5.useCallback)(
|
|
798
|
+
(c) => {
|
|
799
|
+
if (!scoped) return;
|
|
800
|
+
setCommentState(c);
|
|
801
|
+
},
|
|
802
|
+
[scoped]
|
|
803
|
+
);
|
|
804
|
+
const clearError = (0, import_react5.useCallback)(() => setError(null), []);
|
|
805
|
+
const submit = (0, import_react5.useCallback)(async () => {
|
|
806
|
+
if (!scoped || !isEnabled || !rating) return;
|
|
807
|
+
setSubmitting(true);
|
|
808
|
+
setError(null);
|
|
809
|
+
const cfg = firstflow.feedback.getConfig();
|
|
810
|
+
const sideCfg = rating === "like" ? cfg.like : cfg.dislike;
|
|
811
|
+
const showTags = sideCfg.showTags === true;
|
|
812
|
+
const showComment = sideCfg.showComment === true;
|
|
813
|
+
const payload = {
|
|
814
|
+
conversationId,
|
|
815
|
+
messageId,
|
|
816
|
+
rating,
|
|
817
|
+
showTags,
|
|
818
|
+
showComment,
|
|
819
|
+
tags: showTags && selectedTags.length ? [...new Set(selectedTags)] : void 0,
|
|
820
|
+
comment: showComment && comment.trim() ? comment.trim() : void 0,
|
|
821
|
+
messagePreview: messagePreview.slice(0, 2e3),
|
|
822
|
+
metadata
|
|
823
|
+
};
|
|
824
|
+
try {
|
|
825
|
+
await firstflow.feedback.submit(payload);
|
|
826
|
+
try {
|
|
827
|
+
localStorage.setItem(
|
|
828
|
+
storageKey(firstflow.agentId, messageId),
|
|
829
|
+
JSON.stringify({ rating, tags: selectedTags, comment })
|
|
830
|
+
);
|
|
831
|
+
} catch {
|
|
832
|
+
}
|
|
833
|
+
setSubmitted(true);
|
|
834
|
+
} catch (e) {
|
|
835
|
+
setError(e instanceof Error ? e.message : "Failed to send feedback");
|
|
836
|
+
} finally {
|
|
837
|
+
setSubmitting(false);
|
|
838
|
+
}
|
|
839
|
+
}, [
|
|
840
|
+
scoped,
|
|
841
|
+
isEnabled,
|
|
842
|
+
rating,
|
|
843
|
+
conversationId,
|
|
844
|
+
messageId,
|
|
845
|
+
messagePreview,
|
|
846
|
+
metadata,
|
|
847
|
+
selectedTags,
|
|
848
|
+
comment,
|
|
849
|
+
firstflow,
|
|
850
|
+
isEnabled
|
|
851
|
+
]);
|
|
852
|
+
if (!scoped) {
|
|
853
|
+
return firstflow.feedback;
|
|
854
|
+
}
|
|
855
|
+
return {
|
|
856
|
+
rating,
|
|
857
|
+
selectedTags,
|
|
858
|
+
comment,
|
|
859
|
+
submitting,
|
|
860
|
+
submitted,
|
|
861
|
+
error,
|
|
862
|
+
setRating,
|
|
863
|
+
toggleTag,
|
|
864
|
+
setComment,
|
|
865
|
+
submit,
|
|
866
|
+
clearError,
|
|
867
|
+
isEnabled,
|
|
868
|
+
config,
|
|
869
|
+
sideConfig
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// src/issue/hooks/useFirstflowIssueReporter.ts
|
|
874
|
+
var import_react6 = require("react");
|
|
875
|
+
function useFirstflowIssueReporter() {
|
|
876
|
+
const { reporter, isModalOpen, openModal, closeModal } = useIssueReporterContext();
|
|
877
|
+
const open = (0, import_react6.useCallback)(
|
|
878
|
+
(options) => {
|
|
879
|
+
openModal(options);
|
|
880
|
+
},
|
|
881
|
+
[openModal]
|
|
882
|
+
);
|
|
883
|
+
const reportIssue = (0, import_react6.useCallback)(
|
|
884
|
+
async (data) => {
|
|
885
|
+
await reporter.reportIssue(data);
|
|
886
|
+
},
|
|
887
|
+
[reporter]
|
|
888
|
+
);
|
|
889
|
+
const submit = reportIssue;
|
|
890
|
+
return {
|
|
891
|
+
reporter,
|
|
892
|
+
open,
|
|
893
|
+
reportIssue,
|
|
894
|
+
submit,
|
|
895
|
+
config: reporter.getConfig(),
|
|
896
|
+
isModalOpen,
|
|
897
|
+
closeModal
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// src/issue/issue.ts
|
|
902
|
+
var issue_exports = {};
|
|
903
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
904
|
+
0 && (module.exports = {
|
|
905
|
+
FEEDBACK_SUBMITTED,
|
|
906
|
+
ISSUE_SUBMITTED,
|
|
907
|
+
Issue,
|
|
908
|
+
IssueReporter,
|
|
909
|
+
IssueReporterProvider,
|
|
910
|
+
createIssueReporter,
|
|
911
|
+
useFirstflowFeedback,
|
|
912
|
+
useFirstflowIssueForm,
|
|
913
|
+
useFirstflowIssueReporter,
|
|
914
|
+
useIssueReporterContext
|
|
915
|
+
});
|