@eshal-bot/chat-widget 0.1.20 → 0.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chat-widget.esm.js +5 -5
- package/dist/chat-widget.js +610 -495
- package/dist/chat-widget.min.js +7 -7
- package/dist/chat-widget.umd.js +5 -5
- package/dist/index.d.ts +4 -3
- package/package.json +1 -1
package/dist/chat-widget.js
CHANGED
|
@@ -1292,6 +1292,197 @@
|
|
|
1292
1292
|
return (_ONBOARDING_FIELD_ORD = ONBOARDING_FIELD_ORDER[key]) !== null && _ONBOARDING_FIELD_ORD !== void 0 ? _ONBOARDING_FIELD_ORD : 99;
|
|
1293
1293
|
};
|
|
1294
1294
|
|
|
1295
|
+
const createSession = async config => {
|
|
1296
|
+
// Initialize chat session if needed
|
|
1297
|
+
if (config.sessionUrl) {
|
|
1298
|
+
try {
|
|
1299
|
+
const response = await fetch(config.sessionUrl, {
|
|
1300
|
+
method: "POST",
|
|
1301
|
+
headers: _objectSpread2({
|
|
1302
|
+
"Content-Type": "application/json"
|
|
1303
|
+
}, config.apiKey && {
|
|
1304
|
+
Authorization: "Bearer ".concat(config.apiKey)
|
|
1305
|
+
}),
|
|
1306
|
+
body: JSON.stringify({
|
|
1307
|
+
userId: config.userId,
|
|
1308
|
+
userName: config.userName,
|
|
1309
|
+
userEmail: config.userEmail
|
|
1310
|
+
}),
|
|
1311
|
+
credentials: "include"
|
|
1312
|
+
});
|
|
1313
|
+
const data = await response.json();
|
|
1314
|
+
return data.sessionId;
|
|
1315
|
+
} catch (error) {
|
|
1316
|
+
console.error("Session creation error:", error);
|
|
1317
|
+
return null;
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
return null;
|
|
1321
|
+
};
|
|
1322
|
+
|
|
1323
|
+
/**
|
|
1324
|
+
* Fetches conversation history for a given org and conversation
|
|
1325
|
+
* @param {string} apiBaseUrl - Base URL for the API
|
|
1326
|
+
* @param {string} orgId - Organization ID
|
|
1327
|
+
* @param {string} conversationId - Conversation ID
|
|
1328
|
+
* @returns {Promise<Array>} Array of messages
|
|
1329
|
+
*/
|
|
1330
|
+
const fetchConversationHistory = async (apiBaseUrl, orgId, conversationId) => {
|
|
1331
|
+
if (!apiBaseUrl || !orgId || !conversationId) {
|
|
1332
|
+
throw new Error("apiBaseUrl, orgId, and conversationId are required");
|
|
1333
|
+
}
|
|
1334
|
+
const url = "".concat(apiBaseUrl.replace(/\/$/, ''), "/api/v1/conversations/").concat(orgId, "/").concat(conversationId);
|
|
1335
|
+
const response = await fetch(url, {
|
|
1336
|
+
method: "GET",
|
|
1337
|
+
headers: {
|
|
1338
|
+
"Content-Type": "application/json"
|
|
1339
|
+
},
|
|
1340
|
+
credentials: "include"
|
|
1341
|
+
});
|
|
1342
|
+
if (response.status === 404) {
|
|
1343
|
+
return []; // No history for this conversation yet
|
|
1344
|
+
}
|
|
1345
|
+
if (!response.ok) {
|
|
1346
|
+
throw new Error("HTTP error! status: ".concat(response.status));
|
|
1347
|
+
}
|
|
1348
|
+
const data = await response.json();
|
|
1349
|
+
return data.messages || [];
|
|
1350
|
+
};
|
|
1351
|
+
|
|
1352
|
+
/**
|
|
1353
|
+
* Fetches agent configuration from the deploy-agent endpoint
|
|
1354
|
+
* @param {string} apiBaseUrl - Base URL for the API
|
|
1355
|
+
* @param {string} orgId - Organization ID
|
|
1356
|
+
* @returns {Promise<object>} Agent configuration object
|
|
1357
|
+
*/
|
|
1358
|
+
const fetchAgentConfig = async (apiBaseUrl, orgId) => {
|
|
1359
|
+
if (!apiBaseUrl || !orgId) {
|
|
1360
|
+
throw new Error("apiBaseUrl and orgId are required");
|
|
1361
|
+
}
|
|
1362
|
+
try {
|
|
1363
|
+
const url = "".concat(apiBaseUrl.replace(/\/$/, ''), "/api/v1/deploy-agent/").concat(orgId);
|
|
1364
|
+
const response = await fetch(url, {
|
|
1365
|
+
method: "GET",
|
|
1366
|
+
headers: {
|
|
1367
|
+
"Content-Type": "application/json"
|
|
1368
|
+
},
|
|
1369
|
+
credentials: "include"
|
|
1370
|
+
});
|
|
1371
|
+
if (!response.ok) {
|
|
1372
|
+
throw new Error("HTTP error! status: ".concat(response.status));
|
|
1373
|
+
}
|
|
1374
|
+
const data = await response.json();
|
|
1375
|
+
return data;
|
|
1376
|
+
} catch (error) {
|
|
1377
|
+
console.error("Failed to fetch agent configuration:", error);
|
|
1378
|
+
throw error;
|
|
1379
|
+
}
|
|
1380
|
+
};
|
|
1381
|
+
|
|
1382
|
+
const SESSION_KEY = 'eshal_chat_session';
|
|
1383
|
+
const getTimeoutMs = (value, unit) => {
|
|
1384
|
+
if (value === undefined || value === null || !unit) return null;
|
|
1385
|
+
const multipliers = {
|
|
1386
|
+
MINUTES: 60 * 1000,
|
|
1387
|
+
HOURS: 60 * 60 * 1000,
|
|
1388
|
+
DAYS: 24 * 60 * 60 * 1000
|
|
1389
|
+
};
|
|
1390
|
+
const upperUnit = unit.toUpperCase();
|
|
1391
|
+
const multiplier = multipliers[upperUnit];
|
|
1392
|
+
if (multiplier === undefined) {
|
|
1393
|
+
console.warn("[Session] Unknown timeout unit: ".concat(unit));
|
|
1394
|
+
return null;
|
|
1395
|
+
}
|
|
1396
|
+
return Number(value) * multiplier;
|
|
1397
|
+
};
|
|
1398
|
+
const getSession = orgId => {
|
|
1399
|
+
try {
|
|
1400
|
+
const raw = localStorage.getItem("".concat(SESSION_KEY, "_").concat(orgId));
|
|
1401
|
+
if (!raw) return null;
|
|
1402
|
+
return JSON.parse(raw);
|
|
1403
|
+
} catch (_unused) {
|
|
1404
|
+
return null;
|
|
1405
|
+
}
|
|
1406
|
+
};
|
|
1407
|
+
const isSessionValid = (session, timeoutValue, timeoutUnit) => {
|
|
1408
|
+
if (!session || !session.lastActivity) return false;
|
|
1409
|
+
|
|
1410
|
+
// If no timeout is configured, session is always valid
|
|
1411
|
+
if (timeoutValue === undefined || timeoutValue === null || timeoutValue === 0) {
|
|
1412
|
+
return true;
|
|
1413
|
+
}
|
|
1414
|
+
const timeoutMs = getTimeoutMs(timeoutValue, timeoutUnit);
|
|
1415
|
+
if (!timeoutMs) return true; // Default to valid if unit is missing but value was provided (to avoid accidental wipes)
|
|
1416
|
+
|
|
1417
|
+
const lastActivity = new Date(session.lastActivity).getTime();
|
|
1418
|
+
const elapsed = Date.now() - lastActivity;
|
|
1419
|
+
const isValid = elapsed < timeoutMs;
|
|
1420
|
+
if (!isValid) {
|
|
1421
|
+
console.log("[Session] Session expired. Elapsed: ".concat(Math.floor(elapsed / 1000), "s, Timeout: ").concat(Math.floor(timeoutMs / 1000), "s"));
|
|
1422
|
+
}
|
|
1423
|
+
return isValid;
|
|
1424
|
+
};
|
|
1425
|
+
const saveSession = function (orgId, conversationId) {
|
|
1426
|
+
let extra = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
1427
|
+
try {
|
|
1428
|
+
const now = new Date().toISOString();
|
|
1429
|
+
console.log("[Session] Saving new session: ".concat(conversationId, " for org: ").concat(orgId));
|
|
1430
|
+
localStorage.setItem("".concat(SESSION_KEY, "_").concat(orgId), JSON.stringify(_objectSpread2({
|
|
1431
|
+
conversationId,
|
|
1432
|
+
lastActivity: now,
|
|
1433
|
+
createdAt: now
|
|
1434
|
+
}, extra)));
|
|
1435
|
+
} catch (_unused2) {}
|
|
1436
|
+
};
|
|
1437
|
+
const updateActivity = orgId => {
|
|
1438
|
+
try {
|
|
1439
|
+
const session = getSession(orgId);
|
|
1440
|
+
if (!session) return;
|
|
1441
|
+
session.lastActivity = new Date().toISOString();
|
|
1442
|
+
localStorage.setItem("".concat(SESSION_KEY, "_").concat(orgId), JSON.stringify(session));
|
|
1443
|
+
} catch (_unused3) {}
|
|
1444
|
+
};
|
|
1445
|
+
const markOnboardingCompleted = orgId => {
|
|
1446
|
+
try {
|
|
1447
|
+
const session = getSession(orgId);
|
|
1448
|
+
if (!session) return;
|
|
1449
|
+
session.onboardingCompleted = true;
|
|
1450
|
+
localStorage.setItem("".concat(SESSION_KEY, "_").concat(orgId), JSON.stringify(session));
|
|
1451
|
+
} catch (_unused4) {}
|
|
1452
|
+
};
|
|
1453
|
+
const markCsatSubmitted = orgId => {
|
|
1454
|
+
try {
|
|
1455
|
+
const session = getSession(orgId);
|
|
1456
|
+
if (!session) return;
|
|
1457
|
+
session.csatSubmitted = true;
|
|
1458
|
+
localStorage.setItem("".concat(SESSION_KEY, "_").concat(orgId), JSON.stringify(session));
|
|
1459
|
+
} catch (_unused5) {}
|
|
1460
|
+
};
|
|
1461
|
+
const clearSession = orgId => {
|
|
1462
|
+
try {
|
|
1463
|
+
localStorage.removeItem("".concat(SESSION_KEY, "_").concat(orgId));
|
|
1464
|
+
} catch (_unused6) {}
|
|
1465
|
+
};
|
|
1466
|
+
const savePromptSuggestions = (orgId, prompts) => {
|
|
1467
|
+
try {
|
|
1468
|
+
if (!orgId) return;
|
|
1469
|
+
if (!Array.isArray(prompts) || prompts.length === 0) {
|
|
1470
|
+
localStorage.removeItem("".concat(SESSION_KEY, "_prompts_").concat(orgId));
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1473
|
+
localStorage.setItem("".concat(SESSION_KEY, "_prompts_").concat(orgId), JSON.stringify(prompts));
|
|
1474
|
+
} catch (_unused7) {}
|
|
1475
|
+
};
|
|
1476
|
+
const getPromptSuggestions = orgId => {
|
|
1477
|
+
try {
|
|
1478
|
+
const raw = localStorage.getItem("".concat(SESSION_KEY, "_prompts_").concat(orgId));
|
|
1479
|
+
if (!raw) return [];
|
|
1480
|
+
return JSON.parse(raw) || [];
|
|
1481
|
+
} catch (_unused8) {
|
|
1482
|
+
return [];
|
|
1483
|
+
}
|
|
1484
|
+
};
|
|
1485
|
+
|
|
1295
1486
|
const createMessage = _ref => {
|
|
1296
1487
|
let {
|
|
1297
1488
|
id,
|
|
@@ -1317,51 +1508,6 @@
|
|
|
1317
1508
|
}
|
|
1318
1509
|
return [];
|
|
1319
1510
|
};
|
|
1320
|
-
const validateOnboardingAnswer = (question, answer) => {
|
|
1321
|
-
var _question$fieldType;
|
|
1322
|
-
if (!answer.trim()) {
|
|
1323
|
-
return {
|
|
1324
|
-
isValid: false,
|
|
1325
|
-
errorMessage: "This field is required."
|
|
1326
|
-
};
|
|
1327
|
-
}
|
|
1328
|
-
const fieldType = (_question$fieldType = question.fieldType) === null || _question$fieldType === void 0 ? void 0 : _question$fieldType.toLowerCase();
|
|
1329
|
-
|
|
1330
|
-
// Email validation
|
|
1331
|
-
if (fieldType === "email") {
|
|
1332
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
1333
|
-
if (!emailRegex.test(answer)) {
|
|
1334
|
-
return {
|
|
1335
|
-
isValid: false,
|
|
1336
|
-
errorMessage: "Please enter a valid email address."
|
|
1337
|
-
};
|
|
1338
|
-
}
|
|
1339
|
-
}
|
|
1340
|
-
|
|
1341
|
-
// Phone validation
|
|
1342
|
-
if (fieldType === "phone") {
|
|
1343
|
-
const phoneRegex = /^[+]?[\d\s-]{10,15}$/;
|
|
1344
|
-
if (!phoneRegex.test(answer.replace(/\s/g, ""))) {
|
|
1345
|
-
return {
|
|
1346
|
-
isValid: false,
|
|
1347
|
-
errorMessage: "Please enter a valid phone number."
|
|
1348
|
-
};
|
|
1349
|
-
}
|
|
1350
|
-
}
|
|
1351
|
-
|
|
1352
|
-
// Name validation
|
|
1353
|
-
if (fieldType === "fullname") {
|
|
1354
|
-
if (answer.length > 50) {
|
|
1355
|
-
return {
|
|
1356
|
-
isValid: false,
|
|
1357
|
-
errorMessage: "Name must be less than 50 characters."
|
|
1358
|
-
};
|
|
1359
|
-
}
|
|
1360
|
-
}
|
|
1361
|
-
return {
|
|
1362
|
-
isValid: true
|
|
1363
|
-
};
|
|
1364
|
-
};
|
|
1365
1511
|
const useChatState = _ref2 => {
|
|
1366
1512
|
let {
|
|
1367
1513
|
welcomeMessage,
|
|
@@ -1375,12 +1521,22 @@
|
|
|
1375
1521
|
enableVoiceInteraction = false,
|
|
1376
1522
|
onboardingQuestions = [],
|
|
1377
1523
|
onboardingEnabled = false,
|
|
1378
|
-
collectionPrompt
|
|
1524
|
+
collectionPrompt,
|
|
1525
|
+
inactivityTimeoutValue,
|
|
1526
|
+
inactivityTimeoutUnit
|
|
1379
1527
|
} = _ref2;
|
|
1380
1528
|
const [isOpen, setIsOpen] = reactExports.useState(autoOpen);
|
|
1381
1529
|
const [isMinimized, setIsMinimized] = reactExports.useState(false);
|
|
1382
1530
|
const [isDark, setIsDark] = reactExports.useState(darkMode);
|
|
1383
1531
|
const [messages, setMessages] = reactExports.useState(() => {
|
|
1532
|
+
// If a valid session exists, start empty — history will be loaded by the session effect
|
|
1533
|
+
try {
|
|
1534
|
+
const existing = getSession(organizationId);
|
|
1535
|
+
// Only start empty if we explicitly know there's a valid session to restore
|
|
1536
|
+
if (inactivityTimeoutValue !== undefined && existing && isSessionValid(existing, inactivityTimeoutValue, inactivityTimeoutUnit)) {
|
|
1537
|
+
return [];
|
|
1538
|
+
}
|
|
1539
|
+
} catch (_unused) {}
|
|
1384
1540
|
if (!welcomeMessage) {
|
|
1385
1541
|
return [];
|
|
1386
1542
|
}
|
|
@@ -1397,23 +1553,37 @@
|
|
|
1397
1553
|
const widgetRef = reactExports.useRef(null);
|
|
1398
1554
|
const messageIdRef = reactExports.useRef(1);
|
|
1399
1555
|
|
|
1400
|
-
// Onboarding state:
|
|
1556
|
+
// Onboarding state: restore from session so it survives page refresh
|
|
1401
1557
|
const [onboardingActive, setOnboardingActive] = reactExports.useState(false);
|
|
1402
|
-
const [onboardingCompleted, setOnboardingCompleted] = reactExports.useState(
|
|
1403
|
-
|
|
1558
|
+
const [onboardingCompleted, setOnboardingCompleted] = reactExports.useState(() => {
|
|
1559
|
+
try {
|
|
1560
|
+
const existing = getSession(organizationId);
|
|
1561
|
+
if (existing && existing.onboardingCompleted) return true;
|
|
1562
|
+
} catch (_unused2) {}
|
|
1563
|
+
return false;
|
|
1564
|
+
});
|
|
1404
1565
|
const [pendingUserIntent, setPendingUserIntent] = reactExports.useState(null);
|
|
1405
1566
|
const [onboardingAnswers, setOnboardingAnswers] = reactExports.useState({});
|
|
1406
|
-
const [hasUserInteracted, setHasUserInteracted] = reactExports.useState(false);
|
|
1407
|
-
const [isProcessingAnswer, setIsProcessingAnswer] = reactExports.useState(false);
|
|
1408
1567
|
const pendingIntentProcessedRef = reactExports.useRef(false);
|
|
1409
1568
|
|
|
1410
1569
|
// Bidi/Voice state
|
|
1570
|
+
const [isConversationLoading, setIsConversationLoading] = reactExports.useState(false);
|
|
1411
1571
|
const [isVoiceSessionActive, setIsVoiceSessionActive] = reactExports.useState(false);
|
|
1412
1572
|
const [bidiMessages, setBidiMessages] = reactExports.useState([]);
|
|
1413
1573
|
const [voiceStatus, setVoiceStatus] = reactExports.useState("idle"); // 'idle' | 'connecting' | 'connected' | 'error'
|
|
1414
1574
|
const [voiceError, setVoiceError] = reactExports.useState(null);
|
|
1415
|
-
const [voiceConclusionForCsat, setVoiceConclusionForCsat] = reactExports.useState(false);
|
|
1416
|
-
const [bidiSessionId] = reactExports.useState(() =>
|
|
1575
|
+
const [voiceConclusionForCsat, setVoiceConclusionForCsat] = reactExports.useState(false);
|
|
1576
|
+
const [bidiSessionId, setBidiSessionId] = reactExports.useState(() => {
|
|
1577
|
+
try {
|
|
1578
|
+
const existing = getSession(organizationId);
|
|
1579
|
+
// Only restore if config is loaded and verified valid
|
|
1580
|
+
if (inactivityTimeoutValue !== undefined && existing && isSessionValid(existing, inactivityTimeoutValue, inactivityTimeoutUnit)) {
|
|
1581
|
+
return existing.conversationId;
|
|
1582
|
+
}
|
|
1583
|
+
} catch (_unused3) {}
|
|
1584
|
+
// Otherwise start with a fresh ID (which may be reverted to restored ID in effect if found valid later)
|
|
1585
|
+
return "widget-session-".concat(Math.random().toString(36).slice(2, 9));
|
|
1586
|
+
});
|
|
1417
1587
|
|
|
1418
1588
|
// Bidi refs
|
|
1419
1589
|
const websocketRef = reactExports.useRef(null);
|
|
@@ -1460,7 +1630,106 @@
|
|
|
1460
1630
|
setIsDark(darkMode);
|
|
1461
1631
|
}, [darkMode]);
|
|
1462
1632
|
|
|
1463
|
-
//
|
|
1633
|
+
// Session manager: on mount, either restore existing session or create a new one
|
|
1634
|
+
const historyLoadedRef = reactExports.useRef(false); // true once fetch is initiated
|
|
1635
|
+
const historyHasMessagesRef = reactExports.useRef(false); // true once history with messages is loaded
|
|
1636
|
+
const inactivityInitializedRef = reactExports.useRef(false); // true after first inactivity effect run
|
|
1637
|
+
reactExports.useEffect(() => {
|
|
1638
|
+
// Wait for all configuration to be ready
|
|
1639
|
+
if (!organizationId || !bidiSessionId || inactivityTimeoutValue === undefined) return;
|
|
1640
|
+
const existing = getSession(organizationId);
|
|
1641
|
+
|
|
1642
|
+
// Check validity ONLY if an existing session was found
|
|
1643
|
+
const hasValidSession = existing && isSessionValid(existing, inactivityTimeoutValue, inactivityTimeoutUnit);
|
|
1644
|
+
if (hasValidSession) {
|
|
1645
|
+
// Ensure the stateful session ID matches the persisted valid session
|
|
1646
|
+
if (bidiSessionId !== existing.conversationId) {
|
|
1647
|
+
setBidiSessionId(existing.conversationId);
|
|
1648
|
+
return;
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
// Restore session — fetch conversation history once
|
|
1652
|
+
if (!historyLoadedRef.current && apiBaseUrl) {
|
|
1653
|
+
historyLoadedRef.current = true;
|
|
1654
|
+
setIsConversationLoading(true);
|
|
1655
|
+
fetchConversationHistory(apiBaseUrl, organizationId, bidiSessionId).then(msgs => {
|
|
1656
|
+
// Filter out onboarding field messages (name/email/phone) so they don't appear as chat bubbles
|
|
1657
|
+
const ONBOARDING_TYPES = ['userName', 'email', 'phone', 'fullname', 'mobileno', 'name'];
|
|
1658
|
+
const chatMsgs = msgs.filter(msg => {
|
|
1659
|
+
const msgType = msg.type || msg.messageType;
|
|
1660
|
+
if (msgType && ONBOARDING_TYPES.includes(msgType)) return false;
|
|
1661
|
+
return true;
|
|
1662
|
+
});
|
|
1663
|
+
if (chatMsgs.length > 0) {
|
|
1664
|
+
historyHasMessagesRef.current = true;
|
|
1665
|
+
const historyMessages = chatMsgs.map((msg, index) => _objectSpread2(_objectSpread2({}, createMessage({
|
|
1666
|
+
id: msg.id || "history-".concat(index, "-").concat(Date.now()),
|
|
1667
|
+
role: msg.role || (msg.sender === 'user' ? 'user' : 'assistant'),
|
|
1668
|
+
content: msg.message || msg.content || ''
|
|
1669
|
+
})), {}, {
|
|
1670
|
+
timestamp: msg.time ? new Date(msg.time) : new Date()
|
|
1671
|
+
}, Array.isArray(msg.prompts) && msg.prompts.length > 0 ? {
|
|
1672
|
+
prompts: msg.prompts
|
|
1673
|
+
} : {}));
|
|
1674
|
+
|
|
1675
|
+
// If the API didn't return prompts on any message, restore from localStorage
|
|
1676
|
+
const hasAnyPrompts = historyMessages.some(m => m.role === 'assistant' && Array.isArray(m.prompts) && m.prompts.length > 0);
|
|
1677
|
+
if (!hasAnyPrompts) {
|
|
1678
|
+
const savedPrompts = getPromptSuggestions(organizationId);
|
|
1679
|
+
if (savedPrompts.length > 0) {
|
|
1680
|
+
// Attach saved prompts to the last assistant message
|
|
1681
|
+
for (let i = historyMessages.length - 1; i >= 0; i -= 1) {
|
|
1682
|
+
if (historyMessages[i].role === 'assistant') {
|
|
1683
|
+
historyMessages[i] = _objectSpread2(_objectSpread2({}, historyMessages[i]), {}, {
|
|
1684
|
+
prompts: savedPrompts
|
|
1685
|
+
});
|
|
1686
|
+
break;
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
setMessages(historyMessages);
|
|
1692
|
+
}
|
|
1693
|
+
}).catch(err => {
|
|
1694
|
+
console.error('[Session] History fetch failed:', err);
|
|
1695
|
+
}).finally(() => {
|
|
1696
|
+
setIsConversationLoading(false);
|
|
1697
|
+
});
|
|
1698
|
+
}
|
|
1699
|
+
} else {
|
|
1700
|
+
// Session is expired, invalid, or non-existent
|
|
1701
|
+
if (!historyLoadedRef.current) {
|
|
1702
|
+
historyLoadedRef.current = true;
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
// If we are currently holding an EXPIRED ID, rotate it
|
|
1706
|
+
if (existing && bidiSessionId === existing.conversationId) {
|
|
1707
|
+
const newId = "widget-session-".concat(Math.random().toString(36).slice(2, 9));
|
|
1708
|
+
console.warn("[Session] Rotating expired ID ".concat(bidiSessionId, " -> ").concat(newId));
|
|
1709
|
+
setBidiSessionId(newId);
|
|
1710
|
+
// Note: saveSession will be called on the next run with the newId
|
|
1711
|
+
} else {
|
|
1712
|
+
// We have a fresh ID (either from initializer or rotation), persist it if not already there
|
|
1713
|
+
if (!existing || existing.conversationId !== bidiSessionId) {
|
|
1714
|
+
saveSession(organizationId, bidiSessionId);
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
}, [organizationId, bidiSessionId, apiBaseUrl, inactivityTimeoutValue, inactivityTimeoutUnit]);
|
|
1719
|
+
|
|
1720
|
+
// Persist prompt suggestions to localStorage so they survive page refresh
|
|
1721
|
+
reactExports.useEffect(() => {
|
|
1722
|
+
if (!organizationId || !messages || messages.length === 0) return;
|
|
1723
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
1724
|
+
const msg = messages[i];
|
|
1725
|
+
if ((msg === null || msg === void 0 ? void 0 : msg.role) === 'assistant' && Array.isArray(msg.prompts) && msg.prompts.length > 0) {
|
|
1726
|
+
savePromptSuggestions(organizationId, msg.prompts);
|
|
1727
|
+
return;
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
}, [messages, organizationId]);
|
|
1731
|
+
|
|
1732
|
+
// Show the onboarding form on first load; bypass it if already completed (persisted in session)
|
|
1464
1733
|
reactExports.useEffect(() => {
|
|
1465
1734
|
if (!onboardingEnabled || onboardingCompleted) return;
|
|
1466
1735
|
const questions = getOnboardingQuestions(onboardingQuestions);
|
|
@@ -1469,9 +1738,9 @@
|
|
|
1469
1738
|
}
|
|
1470
1739
|
}, [onboardingEnabled, onboardingCompleted, onboardingQuestions]);
|
|
1471
1740
|
|
|
1472
|
-
// Sync welcome message updates
|
|
1741
|
+
// Sync welcome message updates — skip entirely when history has been loaded
|
|
1473
1742
|
reactExports.useEffect(() => {
|
|
1474
|
-
if (!welcomeMessage) {
|
|
1743
|
+
if (!welcomeMessage || historyHasMessagesRef.current) {
|
|
1475
1744
|
return;
|
|
1476
1745
|
}
|
|
1477
1746
|
setMessages(prev => {
|
|
@@ -1486,7 +1755,6 @@
|
|
|
1486
1755
|
}
|
|
1487
1756
|
const [first, ...rest] = prev;
|
|
1488
1757
|
if (first.isWelcome) {
|
|
1489
|
-
// Keep the same ID when updating welcome message content
|
|
1490
1758
|
return [_objectSpread2(_objectSpread2({}, first), {}, {
|
|
1491
1759
|
content: welcomeMessage
|
|
1492
1760
|
}), ...rest];
|
|
@@ -1620,10 +1888,12 @@
|
|
|
1620
1888
|
// For regular messages, process the stream
|
|
1621
1889
|
if (apiType === 'inquiry' || apiType === 'complaint' || apiType === 'technical') {
|
|
1622
1890
|
await processStream(stream, setMessages);
|
|
1891
|
+
updateActivity(organizationId);
|
|
1623
1892
|
return {
|
|
1624
1893
|
success: true
|
|
1625
1894
|
};
|
|
1626
1895
|
}
|
|
1896
|
+
updateActivity(organizationId);
|
|
1627
1897
|
return {
|
|
1628
1898
|
success: true
|
|
1629
1899
|
};
|
|
@@ -1652,15 +1922,6 @@
|
|
|
1652
1922
|
}
|
|
1653
1923
|
}
|
|
1654
1924
|
}, [apiBaseUrl, apiKey, getNextMessageId, organizationId, messages, bidiSessionId]);
|
|
1655
|
-
const startOnboarding = reactExports.useCallback(() => {
|
|
1656
|
-
const questions = getOnboardingQuestions(onboardingQuestions);
|
|
1657
|
-
if (questions.length === 0) {
|
|
1658
|
-
return;
|
|
1659
|
-
}
|
|
1660
|
-
setOnboardingActive(true);
|
|
1661
|
-
setCurrentQuestionIndex(0);
|
|
1662
|
-
// Form UI shows all questions at once; no intro/first-question messages
|
|
1663
|
-
}, [onboardingQuestions]);
|
|
1664
1925
|
const handleOnboardingFormSubmit = reactExports.useCallback(async answers => {
|
|
1665
1926
|
const questions = getOnboardingQuestions(onboardingQuestions);
|
|
1666
1927
|
if (questions.length === 0) return;
|
|
@@ -1684,17 +1945,8 @@
|
|
|
1684
1945
|
}
|
|
1685
1946
|
setOnboardingActive(false);
|
|
1686
1947
|
setOnboardingCompleted(true);
|
|
1948
|
+
markOnboardingCompleted(organizationId);
|
|
1687
1949
|
setOnboardingAnswers(prev => _objectSpread2(_objectSpread2({}, prev), answers));
|
|
1688
|
-
|
|
1689
|
-
// Confirmation message disabled - no need to show after onboarding
|
|
1690
|
-
// const successMessage = createMessage({
|
|
1691
|
-
// id: getNextMessageId(),
|
|
1692
|
-
// role: "assistant",
|
|
1693
|
-
// content: "Thank you! I've received your information.",
|
|
1694
|
-
// isProcessing: false,
|
|
1695
|
-
// });
|
|
1696
|
-
// setMessages((prev) => [...prev, successMessage]);
|
|
1697
|
-
|
|
1698
1950
|
if (pendingUserIntent && !pendingIntentProcessedRef.current) {
|
|
1699
1951
|
pendingIntentProcessedRef.current = true;
|
|
1700
1952
|
setTimeout(() => {
|
|
@@ -1702,157 +1954,16 @@
|
|
|
1702
1954
|
setPendingUserIntent(null);
|
|
1703
1955
|
}, 500);
|
|
1704
1956
|
}
|
|
1705
|
-
}, [onboardingQuestions, sendMessage, getNextMessageId, pendingUserIntent]);
|
|
1706
|
-
const handleOnboardingAnswer = reactExports.useCallback(async answer => {
|
|
1707
|
-
if (isProcessingAnswer) {
|
|
1708
|
-
return;
|
|
1709
|
-
}
|
|
1710
|
-
const questions = getOnboardingQuestions(onboardingQuestions);
|
|
1711
|
-
const currentQuestion = questions[currentQuestionIndex];
|
|
1712
|
-
if (!currentQuestion) {
|
|
1713
|
-
console.error("No question found at index", currentQuestionIndex);
|
|
1714
|
-
return;
|
|
1715
|
-
}
|
|
1716
|
-
|
|
1717
|
-
// Validate answer first
|
|
1718
|
-
const validation = validateOnboardingAnswer(currentQuestion, answer);
|
|
1719
|
-
if (!validation.isValid) {
|
|
1720
|
-
const errorMessage = createMessage({
|
|
1721
|
-
id: getNextMessageId(),
|
|
1722
|
-
role: "assistant",
|
|
1723
|
-
content: validation.errorMessage || "Invalid input. Please try again.",
|
|
1724
|
-
isProcessing: false
|
|
1725
|
-
});
|
|
1726
|
-
setMessages(prev => [...prev, errorMessage]);
|
|
1727
|
-
return;
|
|
1728
|
-
}
|
|
1729
|
-
setIsProcessingAnswer(true);
|
|
1730
|
-
const fieldType = currentQuestion.fieldType || "question_".concat(currentQuestionIndex);
|
|
1731
|
-
|
|
1732
|
-
// Add user message to UI
|
|
1733
|
-
const userAnswer = createMessage({
|
|
1734
|
-
id: getNextMessageId(),
|
|
1735
|
-
role: "user",
|
|
1736
|
-
content: answer,
|
|
1737
|
-
timestamp: new Date(),
|
|
1738
|
-
isProcessing: false
|
|
1739
|
-
});
|
|
1740
|
-
setMessages(prev => [...prev, userAnswer]);
|
|
1741
|
-
|
|
1742
|
-
// Save answer locally
|
|
1743
|
-
setOnboardingAnswers(prev => _objectSpread2(_objectSpread2({}, prev), {}, {
|
|
1744
|
-
[fieldType]: answer
|
|
1745
|
-
}));
|
|
1746
|
-
try {
|
|
1747
|
-
// Send to API
|
|
1748
|
-
const response = await sendMessage(answer, fieldType);
|
|
1749
|
-
if (!response || !response.success) {
|
|
1750
|
-
const errorMsg = createMessage({
|
|
1751
|
-
id: getNextMessageId(),
|
|
1752
|
-
role: "assistant",
|
|
1753
|
-
content: (response === null || response === void 0 ? void 0 : response.error) || "Failed to save your answer. Please try again.",
|
|
1754
|
-
isProcessing: false
|
|
1755
|
-
});
|
|
1756
|
-
setMessages(prev => [...prev, errorMsg]);
|
|
1757
|
-
setIsProcessingAnswer(false);
|
|
1758
|
-
return;
|
|
1759
|
-
}
|
|
1760
|
-
const nextIndex = currentQuestionIndex + 1;
|
|
1761
|
-
if (nextIndex < questions.length) {
|
|
1762
|
-
setCurrentQuestionIndex(nextIndex);
|
|
1763
|
-
setIsProcessingAnswer(false);
|
|
1764
|
-
setTimeout(() => {
|
|
1765
|
-
var _nextQuestion$askToTy;
|
|
1766
|
-
const nextQuestion = questions[nextIndex];
|
|
1767
|
-
const questionMessage = createMessage({
|
|
1768
|
-
id: getNextMessageId(),
|
|
1769
|
-
role: "assistant",
|
|
1770
|
-
content: (_nextQuestion$askToTy = nextQuestion.askToType) !== null && _nextQuestion$askToTy !== void 0 ? _nextQuestion$askToTy : nextQuestion,
|
|
1771
|
-
isProcessing: false
|
|
1772
|
-
});
|
|
1773
|
-
setMessages(prev => [...prev, questionMessage]);
|
|
1774
|
-
}, 300);
|
|
1775
|
-
} else {
|
|
1776
|
-
// Onboarding completed
|
|
1777
|
-
setOnboardingActive(false);
|
|
1778
|
-
setOnboardingCompleted(true);
|
|
1779
|
-
setIsProcessingAnswer(false);
|
|
1780
|
-
setTimeout(() => {
|
|
1781
|
-
// Confirmation message disabled - no need to show after onboarding
|
|
1782
|
-
// const successMessage = createMessage({
|
|
1783
|
-
// id: getNextMessageId(),
|
|
1784
|
-
// role: "assistant",
|
|
1785
|
-
// content: "Thank you! I've received your information.",
|
|
1786
|
-
// isProcessing: false,
|
|
1787
|
-
// });
|
|
1788
|
-
// setMessages((prev) => [...prev, successMessage]);
|
|
1789
|
-
|
|
1790
|
-
// If there's a pending user intent, pre-fill input
|
|
1791
|
-
if (pendingUserIntent && !pendingIntentProcessedRef.current) {
|
|
1792
|
-
pendingIntentProcessedRef.current = true;
|
|
1793
|
-
setTimeout(() => {
|
|
1794
|
-
setInputValue(pendingUserIntent);
|
|
1795
|
-
setPendingUserIntent(null);
|
|
1796
|
-
}, 500);
|
|
1797
|
-
}
|
|
1798
|
-
}, 300);
|
|
1799
|
-
}
|
|
1800
|
-
} catch (error) {
|
|
1801
|
-
const errorMsg = createMessage({
|
|
1802
|
-
id: getNextMessageId(),
|
|
1803
|
-
role: "assistant",
|
|
1804
|
-
content: "An error occurred. Please try again.",
|
|
1805
|
-
isProcessing: false
|
|
1806
|
-
});
|
|
1807
|
-
setMessages(prev => [...prev, errorMsg]);
|
|
1808
|
-
setIsProcessingAnswer(false);
|
|
1809
|
-
}
|
|
1810
|
-
}, [onboardingQuestions, currentQuestionIndex, pendingUserIntent, sendMessage, isProcessingAnswer, getNextMessageId]);
|
|
1957
|
+
}, [onboardingQuestions, sendMessage, getNextMessageId, pendingUserIntent, organizationId]);
|
|
1811
1958
|
const handleSmartSubmit = reactExports.useCallback(async () => {
|
|
1812
|
-
const questions = getOnboardingQuestions(onboardingQuestions);
|
|
1813
1959
|
const trimmedInput = inputValue.trim();
|
|
1814
|
-
if (!trimmedInput)
|
|
1815
|
-
return;
|
|
1816
|
-
}
|
|
1817
|
-
|
|
1818
|
-
// Case 1: No onboarding questions OR onboarding already completed OR onboarding disabled
|
|
1819
|
-
if (questions.length === 0 || onboardingCompleted || !onboardingEnabled) {
|
|
1820
|
-
await sendMessage(trimmedInput);
|
|
1821
|
-
return;
|
|
1822
|
-
}
|
|
1823
|
-
|
|
1824
|
-
// Case 2: User's FIRST interaction - start onboarding
|
|
1825
|
-
if (!hasUserInteracted && !onboardingActive) {
|
|
1826
|
-
setHasUserInteracted(true);
|
|
1827
|
-
pendingIntentProcessedRef.current = false;
|
|
1960
|
+
if (!trimmedInput) return;
|
|
1828
1961
|
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
role: "user",
|
|
1833
|
-
content: trimmedInput,
|
|
1834
|
-
timestamp: new Date(),
|
|
1835
|
-
isProcessing: false
|
|
1836
|
-
});
|
|
1837
|
-
setMessages(prev => [...prev, userMessage]);
|
|
1838
|
-
|
|
1839
|
-
// Store the user's intent
|
|
1840
|
-
setPendingUserIntent(trimmedInput);
|
|
1841
|
-
setInputValue("");
|
|
1842
|
-
startOnboarding();
|
|
1843
|
-
return;
|
|
1844
|
-
}
|
|
1845
|
-
|
|
1846
|
-
// Case 3: Onboarding is ACTIVE - handle answer
|
|
1847
|
-
if (onboardingActive) {
|
|
1848
|
-
await handleOnboardingAnswer(trimmedInput);
|
|
1849
|
-
setInputValue("");
|
|
1850
|
-
return;
|
|
1851
|
-
}
|
|
1852
|
-
|
|
1853
|
-
// Default: send as regular message
|
|
1962
|
+
// Onboarding is handled entirely by the OnboardingForm component.
|
|
1963
|
+
// The text input is hidden while onboarding is active, so any message
|
|
1964
|
+
// arriving here is a normal chat message.
|
|
1854
1965
|
await sendMessage(trimmedInput);
|
|
1855
|
-
}, [
|
|
1966
|
+
}, [inputValue, sendMessage]);
|
|
1856
1967
|
const handleSend = reactExports.useCallback(async () => {
|
|
1857
1968
|
await handleSmartSubmit();
|
|
1858
1969
|
}, [handleSmartSubmit]);
|
|
@@ -1871,47 +1982,11 @@
|
|
|
1871
1982
|
};
|
|
1872
1983
|
const handleQuickQuestion = reactExports.useCallback(async question => {
|
|
1873
1984
|
const trimmedQuestion = question === null || question === void 0 ? void 0 : question.trim();
|
|
1874
|
-
if (!trimmedQuestion)
|
|
1875
|
-
return;
|
|
1876
|
-
}
|
|
1877
|
-
const questions = getOnboardingQuestions(onboardingQuestions);
|
|
1878
|
-
|
|
1879
|
-
// Case 1: No onboarding questions OR onboarding already completed OR onboarding disabled
|
|
1880
|
-
if (questions.length === 0 || onboardingCompleted || !onboardingEnabled) {
|
|
1881
|
-
await sendMessage(trimmedQuestion);
|
|
1882
|
-
return;
|
|
1883
|
-
}
|
|
1985
|
+
if (!trimmedQuestion) return;
|
|
1884
1986
|
|
|
1885
|
-
//
|
|
1886
|
-
if (!hasUserInteracted && !onboardingActive) {
|
|
1887
|
-
setHasUserInteracted(true);
|
|
1888
|
-
pendingIntentProcessedRef.current = false;
|
|
1889
|
-
|
|
1890
|
-
// Add user message to chat immediately
|
|
1891
|
-
const userMessage = createMessage({
|
|
1892
|
-
id: getNextMessageId(),
|
|
1893
|
-
role: "user",
|
|
1894
|
-
content: trimmedQuestion,
|
|
1895
|
-
timestamp: new Date(),
|
|
1896
|
-
isProcessing: false
|
|
1897
|
-
});
|
|
1898
|
-
setMessages(prev => [...prev, userMessage]);
|
|
1899
|
-
|
|
1900
|
-
// Store the user's intent
|
|
1901
|
-
setPendingUserIntent(trimmedQuestion);
|
|
1902
|
-
startOnboarding();
|
|
1903
|
-
return;
|
|
1904
|
-
}
|
|
1905
|
-
|
|
1906
|
-
// Case 3: Onboarding is ACTIVE - handle answer
|
|
1907
|
-
if (onboardingActive) {
|
|
1908
|
-
await handleOnboardingAnswer(trimmedQuestion);
|
|
1909
|
-
return;
|
|
1910
|
-
}
|
|
1911
|
-
|
|
1912
|
-
// Default: send as regular message
|
|
1987
|
+
// Quick questions are only visible after onboarding is complete (input is hidden during onboarding)
|
|
1913
1988
|
await sendMessage(trimmedQuestion);
|
|
1914
|
-
}, [
|
|
1989
|
+
}, [sendMessage]);
|
|
1915
1990
|
|
|
1916
1991
|
/**
|
|
1917
1992
|
* Handle user decision for HITL (Human-in-the-Loop)
|
|
@@ -1948,35 +2023,19 @@
|
|
|
1948
2023
|
if (!currentInputMessageIdRef.current) {
|
|
1949
2024
|
return;
|
|
1950
2025
|
}
|
|
1951
|
-
console.log("[BIDI] finalizePendingInputMessage called", {
|
|
1952
|
-
messageId: currentInputMessageIdRef.current
|
|
1953
|
-
});
|
|
1954
2026
|
setBidiMessages(prev => {
|
|
1955
2027
|
const updated = prev.map(message => message.id === currentInputMessageIdRef.current ? _objectSpread2(_objectSpread2({}, message), {}, {
|
|
1956
2028
|
isProcessing: false
|
|
1957
2029
|
}) : message);
|
|
1958
|
-
console.log("[BIDI] finalizePendingInputMessage: updated state", {
|
|
1959
|
-
messageId: currentInputMessageIdRef.current,
|
|
1960
|
-
found: prev.some(m => m.id === currentInputMessageIdRef.current),
|
|
1961
|
-
prevLength: prev.length,
|
|
1962
|
-
newLength: updated.length
|
|
1963
|
-
});
|
|
1964
2030
|
return updated;
|
|
1965
2031
|
});
|
|
1966
2032
|
currentInputMessageIdRef.current = null;
|
|
1967
2033
|
}, []);
|
|
1968
2034
|
const appendUserTranscription = reactExports.useCallback(function (text) {
|
|
1969
2035
|
let finished = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
|
1970
|
-
console.log("[BIDI] appendUserTranscription called", {
|
|
1971
|
-
text: text === null || text === void 0 ? void 0 : text.substring(0, 50),
|
|
1972
|
-
finished,
|
|
1973
|
-
currentInputId: currentInputMessageIdRef.current
|
|
1974
|
-
});
|
|
1975
|
-
|
|
1976
2036
|
// Mark that user has spoken - now we can process assistant responses
|
|
1977
2037
|
userHasSpokenRef.current = true;
|
|
1978
2038
|
if (!text || typeof text === 'string' && !text.trim()) {
|
|
1979
|
-
console.log("[BIDI] appendUserTranscription: skipping empty text");
|
|
1980
2039
|
if (finished && currentInputMessageIdRef.current) {
|
|
1981
2040
|
setBidiMessages(prev => prev.map(message => message.id === currentInputMessageIdRef.current ? _objectSpread2(_objectSpread2({}, message), {}, {
|
|
1982
2041
|
isProcessing: false
|
|
@@ -2008,17 +2067,9 @@
|
|
|
2008
2067
|
const assistantTime = latestAssistantMessage.timestamp instanceof Date ? latestAssistantMessage.timestamp.getTime() : new Date(latestAssistantMessage.timestamp).getTime();
|
|
2009
2068
|
// Use timestamp 1ms before the assistant message to ensure user appears first
|
|
2010
2069
|
userTimestamp = new Date(assistantTime - 1);
|
|
2011
|
-
console.log("[BIDI] appendUserTranscription: using timestamp before assistant message", {
|
|
2012
|
-
assistantTime: new Date(assistantTime).toISOString(),
|
|
2013
|
-
userTime: userTimestamp.toISOString(),
|
|
2014
|
-
assistantId: latestAssistantMessage.id
|
|
2015
|
-
});
|
|
2016
2070
|
} else {
|
|
2017
2071
|
// No assistant message yet, use current time
|
|
2018
2072
|
userTimestamp = new Date(now);
|
|
2019
|
-
console.log("[BIDI] appendUserTranscription: no assistant message, using current time", {
|
|
2020
|
-
userTime: userTimestamp.toISOString()
|
|
2021
|
-
});
|
|
2022
2073
|
}
|
|
2023
2074
|
const userMessage = createMessage({
|
|
2024
2075
|
id: newId,
|
|
@@ -2039,13 +2090,6 @@
|
|
|
2039
2090
|
result = [...result, userMessage];
|
|
2040
2091
|
// Set ref AFTER adding message to result array
|
|
2041
2092
|
currentInputMessageIdRef.current = newId;
|
|
2042
|
-
console.log("[BIDI] appendUserTranscription: created new user message", {
|
|
2043
|
-
messageId: newId,
|
|
2044
|
-
content: chunk.substring(0, 50),
|
|
2045
|
-
prevLength,
|
|
2046
|
-
newLength: result.length,
|
|
2047
|
-
allMessageIds: result.map(m => m.id)
|
|
2048
|
-
});
|
|
2049
2093
|
} else {
|
|
2050
2094
|
console.warn("[BIDI] appendUserTranscription: message already exists, updating instead", {
|
|
2051
2095
|
messageId: newId
|
|
@@ -2061,18 +2105,10 @@
|
|
|
2061
2105
|
const userMessageExists = result.some(m => m.id === currentInputMessageIdRef.current);
|
|
2062
2106
|
if (userMessageExists) {
|
|
2063
2107
|
result = result.map(message => {
|
|
2064
|
-
var _message$content, _message$content2;
|
|
2065
2108
|
if (message.id !== currentInputMessageIdRef.current) {
|
|
2066
2109
|
return message;
|
|
2067
2110
|
}
|
|
2068
2111
|
const combined = cleanCJKSpaces("".concat(message.content || "").concat(chunk));
|
|
2069
|
-
console.log("[BIDI] appendUserTranscription: updating existing message", {
|
|
2070
|
-
messageId: message.id,
|
|
2071
|
-
oldContent: (_message$content = message.content) === null || _message$content === void 0 ? void 0 : _message$content.substring(0, 30),
|
|
2072
|
-
newContent: combined.substring(0, 50),
|
|
2073
|
-
oldLength: ((_message$content2 = message.content) === null || _message$content2 === void 0 ? void 0 : _message$content2.length) || 0,
|
|
2074
|
-
newLength: combined.length
|
|
2075
|
-
});
|
|
2076
2112
|
return _objectSpread2(_objectSpread2({}, message), {}, {
|
|
2077
2113
|
content: combined,
|
|
2078
2114
|
// CRITICAL: Don't update timestamp when appending content - preserve original timestamp for ordering
|
|
@@ -2107,12 +2143,6 @@
|
|
|
2107
2143
|
});
|
|
2108
2144
|
}
|
|
2109
2145
|
}
|
|
2110
|
-
console.log("[BIDI] appendUserTranscription: state update complete", {
|
|
2111
|
-
prevLength,
|
|
2112
|
-
newLength: result.length,
|
|
2113
|
-
messageIds: result.map(m => m.id),
|
|
2114
|
-
messageRoles: result.map(m => m.role)
|
|
2115
|
-
});
|
|
2116
2146
|
|
|
2117
2147
|
// CRITICAL: Safety check - never return fewer messages than we started with
|
|
2118
2148
|
if (result.length < prevLength) {
|
|
@@ -2141,9 +2171,6 @@
|
|
|
2141
2171
|
return result;
|
|
2142
2172
|
});
|
|
2143
2173
|
if (finished) {
|
|
2144
|
-
console.log("[BIDI] appendUserTranscription: finalizing message", {
|
|
2145
|
-
messageId: currentInputMessageIdRef.current
|
|
2146
|
-
});
|
|
2147
2174
|
currentInputMessageIdRef.current = null;
|
|
2148
2175
|
setIsLoading(true);
|
|
2149
2176
|
}
|
|
@@ -2182,9 +2209,6 @@
|
|
|
2182
2209
|
if (isProcessingAssistantUpdateRef.current) {
|
|
2183
2210
|
// Check if this is the exact same content we're already processing
|
|
2184
2211
|
if (lastProcessedAssistantContentRef.current === contentKey) {
|
|
2185
|
-
console.log("[BIDI] appendAssistantContent: skipping duplicate - already processing", {
|
|
2186
|
-
contentKey
|
|
2187
|
-
});
|
|
2188
2212
|
return;
|
|
2189
2213
|
}
|
|
2190
2214
|
// If it's different content, we need to wait - but for now, let it through
|
|
@@ -2220,9 +2244,6 @@
|
|
|
2220
2244
|
result = result.map(message => message.id === inputMessageIdToFinalize ? _objectSpread2(_objectSpread2({}, message), {}, {
|
|
2221
2245
|
isProcessing: false
|
|
2222
2246
|
}) : message);
|
|
2223
|
-
console.log("[BIDI] appendAssistantContent: finalized input message atomically", {
|
|
2224
|
-
inputMessageId: inputMessageIdToFinalize
|
|
2225
|
-
});
|
|
2226
2247
|
} else {
|
|
2227
2248
|
console.warn("[BIDI] appendAssistantContent: input message not found for finalization", {
|
|
2228
2249
|
inputMessageId: inputMessageIdToFinalize,
|
|
@@ -2258,13 +2279,6 @@
|
|
|
2258
2279
|
result = [...result, assistantMessage];
|
|
2259
2280
|
// Set ref AFTER adding message to result array to ensure consistency
|
|
2260
2281
|
currentAssistantMessageIdRef.current = newId;
|
|
2261
|
-
console.log("[BIDI] appendAssistantContent: created new assistant message", {
|
|
2262
|
-
messageId: newId,
|
|
2263
|
-
content: text.substring(0, 100),
|
|
2264
|
-
prevLength,
|
|
2265
|
-
newLength: result.length,
|
|
2266
|
-
allMessageIds: result.map(m => m.id)
|
|
2267
|
-
});
|
|
2268
2282
|
} else {
|
|
2269
2283
|
console.error("[BIDI] appendAssistantContent: ERROR - message ID collision!", {
|
|
2270
2284
|
messageId: newId,
|
|
@@ -2283,14 +2297,14 @@
|
|
|
2283
2297
|
const assistantMessageExists = result.some(m => m.id === currentAssistantMessageIdRef.current);
|
|
2284
2298
|
if (assistantMessageExists) {
|
|
2285
2299
|
result = result.map(message => {
|
|
2286
|
-
var _message$
|
|
2300
|
+
var _message$content;
|
|
2287
2301
|
if (message.id !== currentAssistantMessageIdRef.current) {
|
|
2288
2302
|
return message;
|
|
2289
2303
|
}
|
|
2290
2304
|
const newContent = "".concat(message.content || "").concat(text);
|
|
2291
2305
|
console.log("[BIDI] appendAssistantContent: updating existing message", {
|
|
2292
2306
|
messageId: message.id,
|
|
2293
|
-
oldContentLength: ((_message$
|
|
2307
|
+
oldContentLength: ((_message$content = message.content) === null || _message$content === void 0 ? void 0 : _message$content.length) || 0,
|
|
2294
2308
|
newContentLength: newContent.length,
|
|
2295
2309
|
textLength: text.length
|
|
2296
2310
|
});
|
|
@@ -2469,8 +2483,34 @@
|
|
|
2469
2483
|
}) : message;
|
|
2470
2484
|
}));
|
|
2471
2485
|
}, []);
|
|
2486
|
+
const stopVoiceSession = reactExports.useCallback(() => {
|
|
2487
|
+
var _audioPlayerNodeRef$c, _audioPlayerContextRe, _audioRecorderNodeRef, _audioRecorderContext;
|
|
2488
|
+
setIsVoiceSessionActive(false);
|
|
2489
|
+
setIsLoading(false);
|
|
2490
|
+
isVoiceSessionActiveRef.current = false;
|
|
2491
|
+
setVoiceStatus("idle");
|
|
2492
|
+
setVoiceError(null);
|
|
2493
|
+
|
|
2494
|
+
// Reset userHasSpoken flag when stopping session
|
|
2495
|
+
userHasSpokenRef.current = false;
|
|
2496
|
+
if (websocketRef.current && websocketRef.current.readyState === WebSocket.OPEN) {
|
|
2497
|
+
websocketRef.current.close();
|
|
2498
|
+
}
|
|
2499
|
+
websocketRef.current = null;
|
|
2500
|
+
(_audioPlayerNodeRef$c = audioPlayerNodeRef.current) === null || _audioPlayerNodeRef$c === void 0 || _audioPlayerNodeRef$c.disconnect();
|
|
2501
|
+
(_audioPlayerContextRe = audioPlayerContextRef.current) === null || _audioPlayerContextRe === void 0 || _audioPlayerContextRe.close();
|
|
2502
|
+
audioPlayerNodeRef.current = null;
|
|
2503
|
+
audioPlayerContextRef.current = null;
|
|
2504
|
+
(_audioRecorderNodeRef = audioRecorderNodeRef.current) === null || _audioRecorderNodeRef === void 0 || _audioRecorderNodeRef.disconnect();
|
|
2505
|
+
(_audioRecorderContext = audioRecorderContextRef.current) === null || _audioRecorderContext === void 0 || _audioRecorderContext.close();
|
|
2506
|
+
audioRecorderNodeRef.current = null;
|
|
2507
|
+
audioRecorderContextRef.current = null;
|
|
2508
|
+
stopMicrophone(micStreamRef.current);
|
|
2509
|
+
micStreamRef.current = null;
|
|
2510
|
+
isAudioReadyRef.current = false;
|
|
2511
|
+
}, []);
|
|
2472
2512
|
const handleBidiEvent = reactExports.useCallback(event => {
|
|
2473
|
-
var _event$content, _event$conclusion_det, _event$
|
|
2513
|
+
var _event$content, _event$conclusion_det, _event$outputTranscri2, _event$content3;
|
|
2474
2514
|
console.log("[BIDI] handleBidiEvent called", {
|
|
2475
2515
|
event,
|
|
2476
2516
|
isVoiceSessionActive: isVoiceSessionActiveRef.current,
|
|
@@ -2495,10 +2535,28 @@
|
|
|
2495
2535
|
setVoiceConclusionForCsat(true);
|
|
2496
2536
|
}
|
|
2497
2537
|
if (event.turnComplete) {
|
|
2498
|
-
console.log("[BIDI] handleBidiEvent: processing turnComplete");
|
|
2499
2538
|
handleTurnComplete();
|
|
2500
2539
|
return;
|
|
2501
2540
|
}
|
|
2541
|
+
|
|
2542
|
+
// Handle conversation conclusion detection from bidi/voice
|
|
2543
|
+
if (typeof conclusionDetected === "boolean" && conclusionDetected) {
|
|
2544
|
+
stopVoiceSession();
|
|
2545
|
+
// Mark the last bidi assistant message with conclusionDetected so ChatWindow picks it up
|
|
2546
|
+
setBidiMessages(prev => {
|
|
2547
|
+
const updated = [...prev];
|
|
2548
|
+
for (let i = updated.length - 1; i >= 0; i--) {
|
|
2549
|
+
if (updated[i].role === "assistant") {
|
|
2550
|
+
updated[i] = _objectSpread2(_objectSpread2({}, updated[i]), {}, {
|
|
2551
|
+
conclusionDetected: true
|
|
2552
|
+
});
|
|
2553
|
+
break;
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
return updated;
|
|
2557
|
+
});
|
|
2558
|
+
return;
|
|
2559
|
+
}
|
|
2502
2560
|
if (event.interrupted) {
|
|
2503
2561
|
console.log("[BIDI] handleBidiEvent: processing interrupted");
|
|
2504
2562
|
handleInterrupted();
|
|
@@ -2517,6 +2575,7 @@
|
|
|
2517
2575
|
|
|
2518
2576
|
// Only call appendUserTranscription if there's actual text or if it's finished
|
|
2519
2577
|
if (transcriptionText || transcriptionFinished) {
|
|
2578
|
+
updateActivity(organizationId);
|
|
2520
2579
|
appendUserTranscription(transcriptionText, transcriptionFinished);
|
|
2521
2580
|
} else {
|
|
2522
2581
|
console.warn("[BIDI] handleBidiEvent: inputTranscription event has no text and not finished", event.inputTranscription);
|
|
@@ -2529,30 +2588,16 @@
|
|
|
2529
2588
|
var _event$outputTranscri, _event$content2;
|
|
2530
2589
|
// Silently ignore assistant responses before user speaks
|
|
2531
2590
|
if ((_event$outputTranscri = event.outputTranscription) !== null && _event$outputTranscri !== void 0 && _event$outputTranscri.text || (_event$content2 = event.content) !== null && _event$content2 !== void 0 && _event$content2.parts) {
|
|
2532
|
-
var _event$outputTranscri2, _event$content3;
|
|
2533
|
-
console.log("[BIDI] handleBidiEvent: ignoring assistant response - user hasn't spoken yet", {
|
|
2534
|
-
hasOutputTranscription: !!((_event$outputTranscri2 = event.outputTranscription) !== null && _event$outputTranscri2 !== void 0 && _event$outputTranscri2.text),
|
|
2535
|
-
hasContentParts: !!((_event$content3 = event.content) !== null && _event$content3 !== void 0 && _event$content3.parts)
|
|
2536
|
-
});
|
|
2537
2591
|
return;
|
|
2538
2592
|
}
|
|
2539
2593
|
}
|
|
2540
|
-
if ((_event$
|
|
2541
|
-
console.log("[BIDI] handleBidiEvent: processing outputTranscription", {
|
|
2542
|
-
text: event.outputTranscription.text.substring(0, 50),
|
|
2543
|
-
finished: event.outputTranscription.finished
|
|
2544
|
-
});
|
|
2594
|
+
if ((_event$outputTranscri2 = event.outputTranscription) !== null && _event$outputTranscri2 !== void 0 && _event$outputTranscri2.text) {
|
|
2545
2595
|
appendAssistantContent(event.outputTranscription.text, Boolean(event.outputTranscription.finished));
|
|
2546
2596
|
}
|
|
2547
|
-
if ((_event$
|
|
2548
|
-
var _event$
|
|
2597
|
+
if ((_event$content3 = event.content) !== null && _event$content3 !== void 0 && _event$content3.parts) {
|
|
2598
|
+
var _event$content4;
|
|
2549
2599
|
// Check if the content has a finished flag, or if the event itself indicates completion
|
|
2550
|
-
const contentFinished = ((_event$
|
|
2551
|
-
console.log("[BIDI] handleBidiEvent: processing content parts", {
|
|
2552
|
-
partsCount: event.content.parts.length,
|
|
2553
|
-
contentFinished,
|
|
2554
|
-
eventFinished: event.finished
|
|
2555
|
-
});
|
|
2600
|
+
const contentFinished = ((_event$content4 = event.content) === null || _event$content4 === void 0 ? void 0 : _event$content4.finished) || event.finished || false;
|
|
2556
2601
|
handleContentParts(event.content.parts, contentFinished);
|
|
2557
2602
|
}
|
|
2558
2603
|
|
|
@@ -2560,7 +2605,7 @@
|
|
|
2560
2605
|
if (!event.turnComplete && !event.interrupted && !event.inputTranscription && !event.outputTranscription && !event.content) {
|
|
2561
2606
|
console.warn("[BIDI] handleBidiEvent: event had no recognized fields", event);
|
|
2562
2607
|
}
|
|
2563
|
-
}, [appendAssistantContent, appendUserTranscription, handleContentParts, handleInterrupted, handleTurnComplete]);
|
|
2608
|
+
}, [appendAssistantContent, appendUserTranscription, handleContentParts, handleInterrupted, handleTurnComplete, stopVoiceSession]);
|
|
2564
2609
|
const handleRecorderAudio = reactExports.useCallback(pcmData => {
|
|
2565
2610
|
const ws = websocketRef.current;
|
|
2566
2611
|
if (!isVoiceSessionActiveRef.current || !ws || ws.readyState !== WebSocket.OPEN) {
|
|
@@ -2692,37 +2737,6 @@
|
|
|
2692
2737
|
setVoiceStatus("error");
|
|
2693
2738
|
}
|
|
2694
2739
|
}, [enableVoiceInteraction, organizationId, welcomeMessage, connectWebsocket, initializeAudioPipeline, messages]);
|
|
2695
|
-
const stopVoiceSession = reactExports.useCallback(() => {
|
|
2696
|
-
var _audioPlayerNodeRef$c, _audioPlayerContextRe, _audioRecorderNodeRef, _audioRecorderContext;
|
|
2697
|
-
setIsVoiceSessionActive(false);
|
|
2698
|
-
setIsLoading(false);
|
|
2699
|
-
isVoiceSessionActiveRef.current = false;
|
|
2700
|
-
setVoiceStatus("idle");
|
|
2701
|
-
setVoiceError(null);
|
|
2702
|
-
|
|
2703
|
-
// Reset userHasSpoken flag when stopping session
|
|
2704
|
-
userHasSpokenRef.current = false;
|
|
2705
|
-
if (websocketRef.current && websocketRef.current.readyState === WebSocket.OPEN) {
|
|
2706
|
-
websocketRef.current.close();
|
|
2707
|
-
}
|
|
2708
|
-
websocketRef.current = null;
|
|
2709
|
-
(_audioPlayerNodeRef$c = audioPlayerNodeRef.current) === null || _audioPlayerNodeRef$c === void 0 || _audioPlayerNodeRef$c.disconnect();
|
|
2710
|
-
(_audioPlayerContextRe = audioPlayerContextRef.current) === null || _audioPlayerContextRe === void 0 || _audioPlayerContextRe.close();
|
|
2711
|
-
audioPlayerNodeRef.current = null;
|
|
2712
|
-
audioPlayerContextRef.current = null;
|
|
2713
|
-
(_audioRecorderNodeRef = audioRecorderNodeRef.current) === null || _audioRecorderNodeRef === void 0 || _audioRecorderNodeRef.disconnect();
|
|
2714
|
-
(_audioRecorderContext = audioRecorderContextRef.current) === null || _audioRecorderContext === void 0 || _audioRecorderContext.close();
|
|
2715
|
-
audioRecorderNodeRef.current = null;
|
|
2716
|
-
audioRecorderContextRef.current = null;
|
|
2717
|
-
stopMicrophone(micStreamRef.current);
|
|
2718
|
-
micStreamRef.current = null;
|
|
2719
|
-
isAudioReadyRef.current = false;
|
|
2720
|
-
}, []);
|
|
2721
|
-
|
|
2722
|
-
// Keep ref updated so handleBidiEvent (defined earlier) can call stopVoiceSession without TDZ
|
|
2723
|
-
reactExports.useEffect(() => {
|
|
2724
|
-
stopVoiceSessionRef.current = stopVoiceSession;
|
|
2725
|
-
}, [stopVoiceSession]);
|
|
2726
2740
|
const handleVoiceToggle = reactExports.useCallback(() => {
|
|
2727
2741
|
if (isVoiceSessionActive) {
|
|
2728
2742
|
stopVoiceSession();
|
|
@@ -2750,6 +2764,7 @@
|
|
|
2750
2764
|
type: "text",
|
|
2751
2765
|
text: trimmed
|
|
2752
2766
|
}));
|
|
2767
|
+
updateActivity(organizationId);
|
|
2753
2768
|
setIsLoading(true);
|
|
2754
2769
|
const userMessage = createMessage({
|
|
2755
2770
|
id: "bidi-user-".concat(Date.now()),
|
|
@@ -2782,6 +2797,116 @@
|
|
|
2782
2797
|
isAudioReadyRef.current = false;
|
|
2783
2798
|
};
|
|
2784
2799
|
}, []);
|
|
2800
|
+
|
|
2801
|
+
// Reset entire conversation state after CSAT / closing — mirrors preview behaviour
|
|
2802
|
+
const resetConversation = reactExports.useCallback(() => {
|
|
2803
|
+
// Clear persisted session and prompt suggestions so the next mount starts fresh
|
|
2804
|
+
clearSession(organizationId);
|
|
2805
|
+
savePromptSuggestions(organizationId, []);
|
|
2806
|
+
|
|
2807
|
+
// Reset messages
|
|
2808
|
+
setMessages([]);
|
|
2809
|
+
setBidiMessages([]);
|
|
2810
|
+
|
|
2811
|
+
// Reset onboarding
|
|
2812
|
+
setOnboardingActive(false);
|
|
2813
|
+
setOnboardingCompleted(false);
|
|
2814
|
+
setOnboardingAnswers({});
|
|
2815
|
+
setPendingUserIntent(null);
|
|
2816
|
+
pendingIntentProcessedRef.current = false;
|
|
2817
|
+
userHasSpokenRef.current = false;
|
|
2818
|
+
historyLoadedRef.current = false;
|
|
2819
|
+
historyHasMessagesRef.current = false;
|
|
2820
|
+
|
|
2821
|
+
// Create a new session id and persist it
|
|
2822
|
+
const newSessionId = "widget-session-".concat(Math.random().toString(36).slice(2, 9));
|
|
2823
|
+
saveSession(organizationId, newSessionId);
|
|
2824
|
+
setBidiSessionId(newSessionId);
|
|
2825
|
+
|
|
2826
|
+
// Re-show the welcome message
|
|
2827
|
+
if (welcomeMessage && welcomeMessage.trim() !== "") {
|
|
2828
|
+
setMessages([_objectSpread2(_objectSpread2({}, createMessage({
|
|
2829
|
+
id: Date.now().toString(),
|
|
2830
|
+
role: "assistant",
|
|
2831
|
+
content: welcomeMessage
|
|
2832
|
+
})), {}, {
|
|
2833
|
+
isWelcome: true
|
|
2834
|
+
})]);
|
|
2835
|
+
}
|
|
2836
|
+
|
|
2837
|
+
// Re-trigger onboarding if enabled
|
|
2838
|
+
if (onboardingEnabled) {
|
|
2839
|
+
const questions = getOnboardingQuestions(onboardingQuestions);
|
|
2840
|
+
if (questions.length > 0) {
|
|
2841
|
+
setOnboardingActive(true);
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
}, [organizationId, welcomeMessage, onboardingEnabled, onboardingQuestions]);
|
|
2845
|
+
|
|
2846
|
+
// Keep a stable ref to resetConversation so the timer effect does not need to
|
|
2847
|
+
// list it as a dependency (avoids spurious re-runs — and spurious updateActivity
|
|
2848
|
+
// calls — whenever resetConversation's identity changes due to parent re-renders).
|
|
2849
|
+
const resetConversationRef = reactExports.useRef(resetConversation);
|
|
2850
|
+
reactExports.useEffect(() => {
|
|
2851
|
+
resetConversationRef.current = resetConversation;
|
|
2852
|
+
}, [resetConversation]);
|
|
2853
|
+
|
|
2854
|
+
// Precise inactivity timeout: schedules a setTimeout for exact remaining time,
|
|
2855
|
+
// then reschedules itself after each check (handles activity updates in localStorage).
|
|
2856
|
+
//
|
|
2857
|
+
// Corner cases handled:
|
|
2858
|
+
// - Initial mount / page refresh: reads stored lastActivity and checks if already
|
|
2859
|
+
// expired → resets immediately, otherwise schedules for the remaining time.
|
|
2860
|
+
// - Config change (value/unit updated): refreshes lastActivity to NOW so the new
|
|
2861
|
+
// timeout starts fresh WITHOUT resetting the conversation or creating a new session ID.
|
|
2862
|
+
// - orgId change: resets the initialized flag so the next run is treated as initial mount.
|
|
2863
|
+
reactExports.useEffect(() => {
|
|
2864
|
+
// Reset initialized flag when org changes so we do an immediate expiry check for the new org
|
|
2865
|
+
inactivityInitializedRef.current = false;
|
|
2866
|
+
}, [organizationId]);
|
|
2867
|
+
reactExports.useEffect(() => {
|
|
2868
|
+
if (!organizationId || !inactivityTimeoutValue) return;
|
|
2869
|
+
const timeoutMs = getTimeoutMs(inactivityTimeoutValue, inactivityTimeoutUnit);
|
|
2870
|
+
if (!timeoutMs) return;
|
|
2871
|
+
const isConfigChange = inactivityInitializedRef.current;
|
|
2872
|
+
inactivityInitializedRef.current = true;
|
|
2873
|
+
if (isConfigChange) {
|
|
2874
|
+
// The actual timeout config (value/unit) changed at runtime — refresh lastActivity
|
|
2875
|
+
// to now so the new timeout period starts from this moment, preserving the session.
|
|
2876
|
+
const existing = getSession(organizationId);
|
|
2877
|
+
if (existing) {
|
|
2878
|
+
updateActivity(organizationId);
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
// On page refresh or initial mount, isConfigChange is false so we do NOT touch
|
|
2882
|
+
// lastActivity — the stored timestamp is used as-is to calculate remaining time.
|
|
2883
|
+
|
|
2884
|
+
let timerId = null;
|
|
2885
|
+
const scheduleCheck = () => {
|
|
2886
|
+
const session = getSession(organizationId);
|
|
2887
|
+
if (!session) return;
|
|
2888
|
+
const lastActivityTime = new Date(session.lastActivity).getTime();
|
|
2889
|
+
const elapsed = Date.now() - lastActivityTime;
|
|
2890
|
+
const remaining = timeoutMs - elapsed;
|
|
2891
|
+
if (remaining <= 0) {
|
|
2892
|
+
console.warn("[Session] Inactivity timeout reached! Resetting conversation immediately.");
|
|
2893
|
+
resetConversationRef.current();
|
|
2894
|
+
return;
|
|
2895
|
+
}
|
|
2896
|
+
timerId = setTimeout(scheduleCheck, remaining);
|
|
2897
|
+
};
|
|
2898
|
+
|
|
2899
|
+
// On initial mount: check immediately (session may already be expired).
|
|
2900
|
+
// On config change: lastActivity was just set to now, so remaining ≈ timeoutMs.
|
|
2901
|
+
scheduleCheck();
|
|
2902
|
+
return () => {
|
|
2903
|
+
if (timerId) clearTimeout(timerId);
|
|
2904
|
+
};
|
|
2905
|
+
// resetConversation intentionally omitted — accessed via resetConversationRef to
|
|
2906
|
+
// prevent the effect from re-running (and resetting lastActivity) on identity changes.
|
|
2907
|
+
// bidiSessionId IS included so the timer restarts after a session reset.
|
|
2908
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2909
|
+
}, [organizationId, inactivityTimeoutValue, inactivityTimeoutUnit, bidiSessionId]);
|
|
2785
2910
|
const memoizedQuickQuestions = reactExports.useMemo(() => quickQuestions, [quickQuestions]);
|
|
2786
2911
|
|
|
2787
2912
|
// Merge and sort messages from both sources (text and voice) by timestamp
|
|
@@ -2838,6 +2963,7 @@
|
|
|
2838
2963
|
messages: activeMessages,
|
|
2839
2964
|
inputValue,
|
|
2840
2965
|
isLoading,
|
|
2966
|
+
isConversationLoading,
|
|
2841
2967
|
widgetRef,
|
|
2842
2968
|
quickQuestions: memoizedQuickQuestions,
|
|
2843
2969
|
// Bidi/Voice state
|
|
@@ -2862,7 +2988,9 @@
|
|
|
2862
2988
|
handleDecision,
|
|
2863
2989
|
// Bidi/Voice actions
|
|
2864
2990
|
handleVoiceToggle,
|
|
2865
|
-
sendBidiTextMessage
|
|
2991
|
+
sendBidiTextMessage,
|
|
2992
|
+
// Session lifecycle
|
|
2993
|
+
resetConversation
|
|
2866
2994
|
};
|
|
2867
2995
|
};
|
|
2868
2996
|
|
|
@@ -3017,7 +3145,7 @@
|
|
|
3017
3145
|
*/
|
|
3018
3146
|
|
|
3019
3147
|
|
|
3020
|
-
const __iconNode$
|
|
3148
|
+
const __iconNode$a = [
|
|
3021
3149
|
["path", { d: "M2 10v3", key: "1fnikh" }],
|
|
3022
3150
|
["path", { d: "M6 6v11", key: "11sgs0" }],
|
|
3023
3151
|
["path", { d: "M10 3v18", key: "yhl04a" }],
|
|
@@ -3025,7 +3153,7 @@
|
|
|
3025
3153
|
["path", { d: "M18 5v13", key: "123xd1" }],
|
|
3026
3154
|
["path", { d: "M22 10v3", key: "154ddg" }]
|
|
3027
3155
|
];
|
|
3028
|
-
const AudioLines = createLucideIcon("audio-lines", __iconNode$
|
|
3156
|
+
const AudioLines = createLucideIcon("audio-lines", __iconNode$a);
|
|
3029
3157
|
|
|
3030
3158
|
/**
|
|
3031
3159
|
* @license lucide-react v0.575.0 - ISC
|
|
@@ -3035,12 +3163,12 @@
|
|
|
3035
3163
|
*/
|
|
3036
3164
|
|
|
3037
3165
|
|
|
3038
|
-
const __iconNode$
|
|
3166
|
+
const __iconNode$9 = [
|
|
3039
3167
|
["path", { d: "M12 15V3", key: "m9g1x1" }],
|
|
3040
3168
|
["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4", key: "ih7n3h" }],
|
|
3041
3169
|
["path", { d: "m7 10 5 5 5-5", key: "brsn70" }]
|
|
3042
3170
|
];
|
|
3043
|
-
const Download = createLucideIcon("download", __iconNode$
|
|
3171
|
+
const Download = createLucideIcon("download", __iconNode$9);
|
|
3044
3172
|
|
|
3045
3173
|
/**
|
|
3046
3174
|
* @license lucide-react v0.575.0 - ISC
|
|
@@ -3050,12 +3178,12 @@
|
|
|
3050
3178
|
*/
|
|
3051
3179
|
|
|
3052
3180
|
|
|
3053
|
-
const __iconNode$
|
|
3181
|
+
const __iconNode$8 = [
|
|
3054
3182
|
["path", { d: "M15 3h6v6", key: "1q9fwt" }],
|
|
3055
3183
|
["path", { d: "M10 14 21 3", key: "gplh6r" }],
|
|
3056
3184
|
["path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6", key: "a6xqqp" }]
|
|
3057
3185
|
];
|
|
3058
|
-
const ExternalLink = createLucideIcon("external-link", __iconNode$
|
|
3186
|
+
const ExternalLink = createLucideIcon("external-link", __iconNode$8);
|
|
3059
3187
|
|
|
3060
3188
|
/**
|
|
3061
3189
|
* @license lucide-react v0.575.0 - ISC
|
|
@@ -3065,7 +3193,7 @@
|
|
|
3065
3193
|
*/
|
|
3066
3194
|
|
|
3067
3195
|
|
|
3068
|
-
const __iconNode$
|
|
3196
|
+
const __iconNode$7 = [
|
|
3069
3197
|
[
|
|
3070
3198
|
"path",
|
|
3071
3199
|
{
|
|
@@ -3078,7 +3206,7 @@
|
|
|
3078
3206
|
["path", { d: "M16 13H8", key: "t4e002" }],
|
|
3079
3207
|
["path", { d: "M16 17H8", key: "z1uh3a" }]
|
|
3080
3208
|
];
|
|
3081
|
-
const FileText = createLucideIcon("file-text", __iconNode$
|
|
3209
|
+
const FileText = createLucideIcon("file-text", __iconNode$7);
|
|
3082
3210
|
|
|
3083
3211
|
/**
|
|
3084
3212
|
* @license lucide-react v0.575.0 - ISC
|
|
@@ -3088,12 +3216,12 @@
|
|
|
3088
3216
|
*/
|
|
3089
3217
|
|
|
3090
3218
|
|
|
3091
|
-
const __iconNode$
|
|
3219
|
+
const __iconNode$6 = [
|
|
3092
3220
|
["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
|
|
3093
3221
|
["path", { d: "M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20", key: "13o1zl" }],
|
|
3094
3222
|
["path", { d: "M2 12h20", key: "9i4pu4" }]
|
|
3095
3223
|
];
|
|
3096
|
-
const Globe = createLucideIcon("globe", __iconNode$
|
|
3224
|
+
const Globe = createLucideIcon("globe", __iconNode$6);
|
|
3097
3225
|
|
|
3098
3226
|
/**
|
|
3099
3227
|
* @license lucide-react v0.575.0 - ISC
|
|
@@ -3103,8 +3231,8 @@
|
|
|
3103
3231
|
*/
|
|
3104
3232
|
|
|
3105
3233
|
|
|
3106
|
-
const __iconNode$
|
|
3107
|
-
const LoaderCircle = createLucideIcon("loader-circle", __iconNode$
|
|
3234
|
+
const __iconNode$5 = [["path", { d: "M21 12a9 9 0 1 1-6.219-8.56", key: "13zald" }]];
|
|
3235
|
+
const LoaderCircle = createLucideIcon("loader-circle", __iconNode$5);
|
|
3108
3236
|
|
|
3109
3237
|
/**
|
|
3110
3238
|
* @license lucide-react v0.575.0 - ISC
|
|
@@ -3114,7 +3242,7 @@
|
|
|
3114
3242
|
*/
|
|
3115
3243
|
|
|
3116
3244
|
|
|
3117
|
-
const __iconNode$
|
|
3245
|
+
const __iconNode$4 = [
|
|
3118
3246
|
[
|
|
3119
3247
|
"path",
|
|
3120
3248
|
{
|
|
@@ -3123,7 +3251,7 @@
|
|
|
3123
3251
|
}
|
|
3124
3252
|
]
|
|
3125
3253
|
];
|
|
3126
|
-
const MessageCircle = createLucideIcon("message-circle", __iconNode$
|
|
3254
|
+
const MessageCircle = createLucideIcon("message-circle", __iconNode$4);
|
|
3127
3255
|
|
|
3128
3256
|
/**
|
|
3129
3257
|
* @license lucide-react v0.575.0 - ISC
|
|
@@ -3133,7 +3261,7 @@
|
|
|
3133
3261
|
*/
|
|
3134
3262
|
|
|
3135
3263
|
|
|
3136
|
-
const __iconNode$
|
|
3264
|
+
const __iconNode$3 = [
|
|
3137
3265
|
[
|
|
3138
3266
|
"path",
|
|
3139
3267
|
{
|
|
@@ -3143,7 +3271,7 @@
|
|
|
3143
3271
|
],
|
|
3144
3272
|
["path", { d: "m21.854 2.147-10.94 10.939", key: "12cjpa" }]
|
|
3145
3273
|
];
|
|
3146
|
-
const Send = createLucideIcon("send", __iconNode$
|
|
3274
|
+
const Send = createLucideIcon("send", __iconNode$3);
|
|
3147
3275
|
|
|
3148
3276
|
/**
|
|
3149
3277
|
* @license lucide-react v0.575.0 - ISC
|
|
@@ -3153,7 +3281,7 @@
|
|
|
3153
3281
|
*/
|
|
3154
3282
|
|
|
3155
3283
|
|
|
3156
|
-
const __iconNode$
|
|
3284
|
+
const __iconNode$2 = [
|
|
3157
3285
|
["path", { d: "M12 2v2", key: "tus03m" }],
|
|
3158
3286
|
[
|
|
3159
3287
|
"path",
|
|
@@ -3166,7 +3294,7 @@
|
|
|
3166
3294
|
["path", { d: "m19 5-1.256 1.256", key: "1yg6a6" }],
|
|
3167
3295
|
["path", { d: "M20 12h2", key: "1q8mjw" }]
|
|
3168
3296
|
];
|
|
3169
|
-
const SunMoon = createLucideIcon("sun-moon", __iconNode$
|
|
3297
|
+
const SunMoon = createLucideIcon("sun-moon", __iconNode$2);
|
|
3170
3298
|
|
|
3171
3299
|
/**
|
|
3172
3300
|
* @license lucide-react v0.575.0 - ISC
|
|
@@ -3176,7 +3304,7 @@
|
|
|
3176
3304
|
*/
|
|
3177
3305
|
|
|
3178
3306
|
|
|
3179
|
-
const __iconNode$
|
|
3307
|
+
const __iconNode$1 = [
|
|
3180
3308
|
["circle", { cx: "12", cy: "12", r: "4", key: "4exip2" }],
|
|
3181
3309
|
["path", { d: "M12 2v2", key: "tus03m" }],
|
|
3182
3310
|
["path", { d: "M12 20v2", key: "1lh1kg" }],
|
|
@@ -3187,21 +3315,7 @@
|
|
|
3187
3315
|
["path", { d: "m6.34 17.66-1.41 1.41", key: "1m8zz5" }],
|
|
3188
3316
|
["path", { d: "m19.07 4.93-1.41 1.41", key: "1shlcs" }]
|
|
3189
3317
|
];
|
|
3190
|
-
const Sun = createLucideIcon("sun", __iconNode$
|
|
3191
|
-
|
|
3192
|
-
/**
|
|
3193
|
-
* @license lucide-react v0.575.0 - ISC
|
|
3194
|
-
*
|
|
3195
|
-
* This source code is licensed under the ISC license.
|
|
3196
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
3197
|
-
*/
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
const __iconNode$1 = [
|
|
3201
|
-
["path", { d: "M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2", key: "975kel" }],
|
|
3202
|
-
["circle", { cx: "12", cy: "7", r: "4", key: "17ys0d" }]
|
|
3203
|
-
];
|
|
3204
|
-
const User = createLucideIcon("user", __iconNode$1);
|
|
3318
|
+
const Sun = createLucideIcon("sun", __iconNode$1);
|
|
3205
3319
|
|
|
3206
3320
|
/**
|
|
3207
3321
|
* @license lucide-react v0.575.0 - ISC
|
|
@@ -57678,17 +57792,9 @@
|
|
|
57678
57792
|
const messageFontFamily = userMessageBubbleFontFamily !== undefined && userMessageBubbleFontFamily !== null ? userMessageBubbleFontFamily : fontFamily !== undefined && fontFamily !== null ? fontFamily : "Inter";
|
|
57679
57793
|
const messageFontSize = userMessageBubbleFontSize !== undefined && userMessageBubbleFontSize !== null ? userMessageBubbleFontSize : fontSize !== undefined && fontSize !== null ? fontSize : "14px";
|
|
57680
57794
|
useGoogleFont(messageFontFamily);
|
|
57681
|
-
return /*#__PURE__*/jsxRuntimeExports.
|
|
57795
|
+
return /*#__PURE__*/jsxRuntimeExports.jsx("div", {
|
|
57682
57796
|
className: "flex gap-3 flex-row-reverse animate-in",
|
|
57683
|
-
children:
|
|
57684
|
-
className: "flex-shrink-0 mt-1",
|
|
57685
|
-
children: /*#__PURE__*/jsxRuntimeExports.jsx("div", {
|
|
57686
|
-
className: "w-8 h-8 rounded-full flex items-center justify-center ".concat(isDark ? "bg-gray-700" : "bg-gray-200"),
|
|
57687
|
-
children: /*#__PURE__*/jsxRuntimeExports.jsx(User, {
|
|
57688
|
-
className: "w-4 h-4 text-gray-500"
|
|
57689
|
-
})
|
|
57690
|
-
})
|
|
57691
|
-
}), /*#__PURE__*/jsxRuntimeExports.jsxs("div", {
|
|
57797
|
+
children: /*#__PURE__*/jsxRuntimeExports.jsxs("div", {
|
|
57692
57798
|
className: "flex flex-col items-end max-w-[75%]",
|
|
57693
57799
|
children: [/*#__PURE__*/jsxRuntimeExports.jsx("div", {
|
|
57694
57800
|
className: "rounded-2xl px-4 py-2.5 shadow-lg ".concat(isRtl ? "rounded-tl-sm" : "rounded-tr-sm"),
|
|
@@ -57717,7 +57823,7 @@
|
|
|
57717
57823
|
dir: "auto",
|
|
57718
57824
|
children: formatTime(message.timestamp)
|
|
57719
57825
|
})]
|
|
57720
|
-
})
|
|
57826
|
+
})
|
|
57721
57827
|
});
|
|
57722
57828
|
};
|
|
57723
57829
|
const MessageBubble = _ref19 => {
|
|
@@ -58513,10 +58619,12 @@
|
|
|
58513
58619
|
companyName,
|
|
58514
58620
|
apiBaseUrl,
|
|
58515
58621
|
organizationId,
|
|
58516
|
-
onPromptSuggestionClick
|
|
58622
|
+
onPromptSuggestionClick,
|
|
58623
|
+
conversationResetKey
|
|
58517
58624
|
} = _ref;
|
|
58518
58625
|
const messagesEndRef = reactExports.useRef(null);
|
|
58519
58626
|
const shouldAutoScrollRef = reactExports.useRef(true);
|
|
58627
|
+
const hasInitialScrolledRef = reactExports.useRef(false);
|
|
58520
58628
|
const autoScrollKey = reactExports.useMemo(() => messages.map(message => {
|
|
58521
58629
|
var _message$content$leng, _message$content;
|
|
58522
58630
|
return "".concat(message.id, ":").concat((_message$content$leng = (_message$content = message.content) === null || _message$content === void 0 ? void 0 : _message$content.length) !== null && _message$content$leng !== void 0 ? _message$content$leng : 0, ":").concat(message.isProcessing ? 1 : 0);
|
|
@@ -58542,6 +58650,38 @@
|
|
|
58542
58650
|
container.removeEventListener("scroll", updateAutoScrollIntent);
|
|
58543
58651
|
};
|
|
58544
58652
|
}, [updateAutoScrollIntent, messagesContainerRef]);
|
|
58653
|
+
|
|
58654
|
+
// When the conversation resets (new session ID), unlock initial scroll so we
|
|
58655
|
+
// jump to the bottom when the welcome message appears.
|
|
58656
|
+
reactExports.useEffect(() => {
|
|
58657
|
+
if (!conversationResetKey) return;
|
|
58658
|
+
shouldAutoScrollRef.current = true;
|
|
58659
|
+
hasInitialScrolledRef.current = false;
|
|
58660
|
+
}, [conversationResetKey]);
|
|
58661
|
+
|
|
58662
|
+
// Force instant scroll to bottom on first message load (covers history restore + post-reset)
|
|
58663
|
+
reactExports.useEffect(() => {
|
|
58664
|
+
if (hasInitialScrolledRef.current || messages.length === 0 || typeof window === "undefined") {
|
|
58665
|
+
return undefined;
|
|
58666
|
+
}
|
|
58667
|
+
hasInitialScrolledRef.current = true;
|
|
58668
|
+
let frame2;
|
|
58669
|
+
const frame1 = window.requestAnimationFrame(() => {
|
|
58670
|
+
frame2 = window.requestAnimationFrame(() => {
|
|
58671
|
+
var _messagesEndRef$curre;
|
|
58672
|
+
(_messagesEndRef$curre = messagesEndRef.current) === null || _messagesEndRef$curre === void 0 || _messagesEndRef$curre.scrollIntoView({
|
|
58673
|
+
behavior: "instant",
|
|
58674
|
+
block: "end"
|
|
58675
|
+
});
|
|
58676
|
+
shouldAutoScrollRef.current = true;
|
|
58677
|
+
});
|
|
58678
|
+
});
|
|
58679
|
+
return () => {
|
|
58680
|
+
window.cancelAnimationFrame(frame1);
|
|
58681
|
+
window.cancelAnimationFrame(frame2);
|
|
58682
|
+
};
|
|
58683
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
58684
|
+
}, [messages.length === 0]);
|
|
58545
58685
|
reactExports.useEffect(() => {
|
|
58546
58686
|
if (!shouldAutoScrollRef.current) {
|
|
58547
58687
|
return undefined;
|
|
@@ -58550,8 +58690,8 @@
|
|
|
58550
58690
|
return undefined;
|
|
58551
58691
|
}
|
|
58552
58692
|
const frame = window.requestAnimationFrame(() => {
|
|
58553
|
-
var _messagesEndRef$
|
|
58554
|
-
(_messagesEndRef$
|
|
58693
|
+
var _messagesEndRef$curre2;
|
|
58694
|
+
(_messagesEndRef$curre2 = messagesEndRef.current) === null || _messagesEndRef$curre2 === void 0 || _messagesEndRef$curre2.scrollIntoView({
|
|
58555
58695
|
behavior: "smooth",
|
|
58556
58696
|
block: "end"
|
|
58557
58697
|
});
|
|
@@ -58703,6 +58843,7 @@
|
|
|
58703
58843
|
isOpen,
|
|
58704
58844
|
isMinimized,
|
|
58705
58845
|
isLoading,
|
|
58846
|
+
isConversationLoading = false,
|
|
58706
58847
|
placeholder,
|
|
58707
58848
|
textColor,
|
|
58708
58849
|
fontFamily,
|
|
@@ -58778,13 +58919,13 @@
|
|
|
58778
58919
|
const handleChange = event => {
|
|
58779
58920
|
setInputValue(event.target.value);
|
|
58780
58921
|
};
|
|
58781
|
-
const isDisabled = !inputValue.trim() || isLoading;
|
|
58922
|
+
const isDisabled = !inputValue.trim() || isLoading || isConversationLoading;
|
|
58782
58923
|
const separatorColor = isDark ? "#374151" : "#e5e7eb";
|
|
58783
58924
|
|
|
58784
58925
|
// Check if onboarding flow exists and is not completed
|
|
58785
58926
|
const hasOnboardingQuestions = onboardingQuestions && onboardingQuestions.length > 0;
|
|
58786
58927
|
const isOnboardingIncomplete = hasOnboardingQuestions && !onboardingCompleted;
|
|
58787
|
-
const isVoiceButtonDisabled = csatVisible || onboardingEnabled && isOnboardingIncomplete || voiceStatus === "connecting";
|
|
58928
|
+
const isVoiceButtonDisabled = csatVisible || isConversationLoading || onboardingEnabled && isOnboardingIncomplete || voiceStatus === "connecting";
|
|
58788
58929
|
|
|
58789
58930
|
// Determine tooltip message
|
|
58790
58931
|
const getTooltipMessage = () => {
|
|
@@ -58965,9 +59106,9 @@
|
|
|
58965
59106
|
onFocus: () => setIsFocused(true),
|
|
58966
59107
|
onBlur: () => setIsFocused(false),
|
|
58967
59108
|
onKeyDown: handleKeyPress,
|
|
58968
|
-
placeholder: placeholder || "Type your message...",
|
|
59109
|
+
placeholder: isConversationLoading ? "Agent is loading..." : placeholder || "Type your message...",
|
|
58969
59110
|
rows: 1,
|
|
58970
|
-
disabled: csatVisible || isLoading || isVoiceSessionActive && voiceStatus !== "connected",
|
|
59111
|
+
disabled: csatVisible || isConversationLoading || isLoading || isVoiceSessionActive && voiceStatus !== "connected",
|
|
58971
59112
|
dir: isRtl ? "rtl" : "ltr",
|
|
58972
59113
|
className: "w-full px-4 bg-transparent rounded-xl leading-normal ".concat(isDark ? "text-gray-100" : "text-gray-900", " placeholder-gray-400 resize-none outline-none scrollbar-hide"),
|
|
58973
59114
|
style: {
|
|
@@ -59722,6 +59863,7 @@
|
|
|
59722
59863
|
companyLogo,
|
|
59723
59864
|
messages,
|
|
59724
59865
|
isLoading,
|
|
59866
|
+
isConversationLoading = false,
|
|
59725
59867
|
inputValue,
|
|
59726
59868
|
setInputValue,
|
|
59727
59869
|
onSend,
|
|
@@ -59774,12 +59916,30 @@
|
|
|
59774
59916
|
csatTriggerType = "ON_END",
|
|
59775
59917
|
csatIdleTimeoutMins = 30,
|
|
59776
59918
|
onPromptSuggestionClick,
|
|
59777
|
-
onboardingEnabled = false
|
|
59919
|
+
onboardingEnabled = false,
|
|
59920
|
+
onResetConversation
|
|
59778
59921
|
} = _ref;
|
|
59779
59922
|
useGoogleFont(fontFamily);
|
|
59780
59923
|
const messagesContainerRef = reactExports.useRef(null);
|
|
59781
59924
|
const [showCsat, setShowCsat] = reactExports.useState(false);
|
|
59782
|
-
const [csatSubmitted, setCsatSubmitted] = reactExports.useState(
|
|
59925
|
+
const [csatSubmitted, setCsatSubmitted] = reactExports.useState(() => {
|
|
59926
|
+
try {
|
|
59927
|
+
var _getSession$csatSubmi, _getSession;
|
|
59928
|
+
return (_getSession$csatSubmi = (_getSession = getSession(organizationId)) === null || _getSession === void 0 ? void 0 : _getSession.csatSubmitted) !== null && _getSession$csatSubmi !== void 0 ? _getSession$csatSubmi : false;
|
|
59929
|
+
} catch (_unused) {
|
|
59930
|
+
return false;
|
|
59931
|
+
}
|
|
59932
|
+
});
|
|
59933
|
+
|
|
59934
|
+
// Sync csatSubmitted from session whenever the conversation changes
|
|
59935
|
+
reactExports.useEffect(() => {
|
|
59936
|
+
try {
|
|
59937
|
+
var _getSession$csatSubmi2, _getSession2;
|
|
59938
|
+
const submitted = (_getSession$csatSubmi2 = (_getSession2 = getSession(organizationId)) === null || _getSession2 === void 0 ? void 0 : _getSession2.csatSubmitted) !== null && _getSession$csatSubmi2 !== void 0 ? _getSession$csatSubmi2 : false;
|
|
59939
|
+
setCsatSubmitted(submitted);
|
|
59940
|
+
if (submitted) setShowCsat(false);
|
|
59941
|
+
} catch (_unused2) {}
|
|
59942
|
+
}, [conversationId, organizationId]);
|
|
59783
59943
|
// const [lastUserMessageTime, setLastUserMessageTime] = useState(null);
|
|
59784
59944
|
|
|
59785
59945
|
// Auto-scroll hook
|
|
@@ -59868,6 +60028,8 @@
|
|
|
59868
60028
|
const handleCsatRating = async (rating, format) => {
|
|
59869
60029
|
setCsatSubmitted(true); // optimistic UI
|
|
59870
60030
|
setShowCsat(false);
|
|
60031
|
+
markCsatSubmitted(organizationId); // persist so page refresh doesn't re-show CSAT
|
|
60032
|
+
|
|
59871
60033
|
try {
|
|
59872
60034
|
const headers = {
|
|
59873
60035
|
"Content-Type": "application/json"
|
|
@@ -59890,6 +60052,8 @@
|
|
|
59890
60052
|
if (!response.ok) {
|
|
59891
60053
|
console.error("CSAT submission failed:", await response.text());
|
|
59892
60054
|
}
|
|
60055
|
+
|
|
60056
|
+
// Session continues after CSAT submission — no reset
|
|
59893
60057
|
} catch (error) {
|
|
59894
60058
|
console.error("CSAT submission error:", error);
|
|
59895
60059
|
}
|
|
@@ -59988,7 +60152,8 @@
|
|
|
59988
60152
|
conciergeName: conciergeName,
|
|
59989
60153
|
companyName: companyName,
|
|
59990
60154
|
apiBaseUrl: apiBaseUrl,
|
|
59991
|
-
organizationId: organizationId
|
|
60155
|
+
organizationId: organizationId,
|
|
60156
|
+
conversationResetKey: conversationId
|
|
59992
60157
|
}), showCsat && csatEnabled && !csatSubmitted && /*#__PURE__*/jsxRuntimeExports.jsx("div", {
|
|
59993
60158
|
className: "",
|
|
59994
60159
|
children: /*#__PURE__*/jsxRuntimeExports.jsx(CSATWidget, {
|
|
@@ -60018,6 +60183,7 @@
|
|
|
60018
60183
|
isOpen: isOpen,
|
|
60019
60184
|
isMinimized: isMinimized,
|
|
60020
60185
|
isLoading: isLoading,
|
|
60186
|
+
isConversationLoading: isConversationLoading,
|
|
60021
60187
|
placeholder: placeholder,
|
|
60022
60188
|
textColor: resolvedTextColor,
|
|
60023
60189
|
fontFamily: fontFamily,
|
|
@@ -60083,64 +60249,6 @@
|
|
|
60083
60249
|
});
|
|
60084
60250
|
};
|
|
60085
60251
|
|
|
60086
|
-
const createSession = async config => {
|
|
60087
|
-
// Initialize chat session if needed
|
|
60088
|
-
if (config.sessionUrl) {
|
|
60089
|
-
try {
|
|
60090
|
-
const response = await fetch(config.sessionUrl, {
|
|
60091
|
-
method: "POST",
|
|
60092
|
-
headers: _objectSpread2({
|
|
60093
|
-
"Content-Type": "application/json"
|
|
60094
|
-
}, config.apiKey && {
|
|
60095
|
-
Authorization: "Bearer ".concat(config.apiKey)
|
|
60096
|
-
}),
|
|
60097
|
-
body: JSON.stringify({
|
|
60098
|
-
userId: config.userId,
|
|
60099
|
-
userName: config.userName,
|
|
60100
|
-
userEmail: config.userEmail
|
|
60101
|
-
}),
|
|
60102
|
-
credentials: "include"
|
|
60103
|
-
});
|
|
60104
|
-
const data = await response.json();
|
|
60105
|
-
return data.sessionId;
|
|
60106
|
-
} catch (error) {
|
|
60107
|
-
console.error("Session creation error:", error);
|
|
60108
|
-
return null;
|
|
60109
|
-
}
|
|
60110
|
-
}
|
|
60111
|
-
return null;
|
|
60112
|
-
};
|
|
60113
|
-
|
|
60114
|
-
/**
|
|
60115
|
-
* Fetches agent configuration from the deploy-agent endpoint
|
|
60116
|
-
* @param {string} apiBaseUrl - Base URL for the API
|
|
60117
|
-
* @param {string} orgId - Organization ID
|
|
60118
|
-
* @returns {Promise<object>} Agent configuration object
|
|
60119
|
-
*/
|
|
60120
|
-
const fetchAgentConfig = async (apiBaseUrl, orgId) => {
|
|
60121
|
-
if (!apiBaseUrl || !orgId) {
|
|
60122
|
-
throw new Error("apiBaseUrl and orgId are required");
|
|
60123
|
-
}
|
|
60124
|
-
try {
|
|
60125
|
-
const url = "".concat(apiBaseUrl.replace(/\/$/, ''), "/api/v1/deploy-agent/").concat(orgId);
|
|
60126
|
-
const response = await fetch(url, {
|
|
60127
|
-
method: "GET",
|
|
60128
|
-
headers: {
|
|
60129
|
-
"Content-Type": "application/json"
|
|
60130
|
-
},
|
|
60131
|
-
credentials: "include"
|
|
60132
|
-
});
|
|
60133
|
-
if (!response.ok) {
|
|
60134
|
-
throw new Error("HTTP error! status: ".concat(response.status));
|
|
60135
|
-
}
|
|
60136
|
-
const data = await response.json();
|
|
60137
|
-
return data;
|
|
60138
|
-
} catch (error) {
|
|
60139
|
-
console.error("Failed to fetch agent configuration:", error);
|
|
60140
|
-
throw error;
|
|
60141
|
-
}
|
|
60142
|
-
};
|
|
60143
|
-
|
|
60144
60252
|
const ChatWidget = _ref => {
|
|
60145
60253
|
var _agentConfig$concierg;
|
|
60146
60254
|
let {
|
|
@@ -60215,7 +60323,7 @@
|
|
|
60215
60323
|
|
|
60216
60324
|
// Map API response to widget props (use props as overrides if provided)
|
|
60217
60325
|
const widgetConfig = reactExports.useMemo(() => {
|
|
60218
|
-
var _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8, _ref9, _ref0, _ref1, _ref10, _ref11, _ref12, _ref13, _ref14, _ref15, _ref16, _ref17, _ref18, _ref19, _ref20, _ref21, _ref22, _ref23, _ref24, _ref25, _ref26, _ref27, _ref28, _ref29, _ref30, _ref31, _ref32, _ref33, _ref34, _ref35, _ref36, _agentConfig$csatEnab, _agentConfig$csatForm, _agentConfig$csatProm, _agentConfig$csatTrig, _agentConfig$csatIdle, _agentConfig$csatFoll, _agentConfig$csatFoll2;
|
|
60326
|
+
var _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8, _ref9, _ref0, _ref1, _ref10, _ref11, _ref12, _ref13, _ref14, _ref15, _ref16, _ref17, _ref18, _ref19, _ref20, _ref21, _ref22, _ref23, _ref24, _agentConfig$inactivi, _agentConfig$inactivi2, _ref25, _ref26, _ref27, _ref28, _ref29, _ref30, _ref31, _ref32, _ref33, _ref34, _ref35, _ref36, _agentConfig$csatEnab, _agentConfig$csatForm, _agentConfig$csatProm, _agentConfig$csatTrig, _agentConfig$csatIdle, _agentConfig$csatFoll, _agentConfig$csatFoll2;
|
|
60219
60327
|
if (!agentConfig) {
|
|
60220
60328
|
return null;
|
|
60221
60329
|
}
|
|
@@ -60249,6 +60357,8 @@
|
|
|
60249
60357
|
allowedDomains: finalAllowedDomains,
|
|
60250
60358
|
launcherPosition: (_ref23 = position !== null && position !== void 0 ? position : agentConfig.launcherPosition) !== null && _ref23 !== void 0 ? _ref23 : "bottom-right",
|
|
60251
60359
|
onboardingEnabled: (_ref24 = onboardingEnabled !== null && onboardingEnabled !== void 0 ? onboardingEnabled : agentConfig.onboardingEnabled) !== null && _ref24 !== void 0 ? _ref24 : false,
|
|
60360
|
+
inactivityTimeoutValue: (_agentConfig$inactivi = agentConfig.inactivityTimeoutValue) !== null && _agentConfig$inactivi !== void 0 ? _agentConfig$inactivi : null,
|
|
60361
|
+
inactivityTimeoutUnit: (_agentConfig$inactivi2 = agentConfig.inactivityTimeoutUnit) !== null && _agentConfig$inactivi2 !== void 0 ? _agentConfig$inactivi2 : null,
|
|
60252
60362
|
agentMessageBubbleFontFamily: (_ref25 = (_ref26 = (_ref27 = agentMessageBubbleFontFamily !== null && agentMessageBubbleFontFamily !== void 0 ? agentMessageBubbleFontFamily : agentConfig.agentMessageBubbleFontFamily) !== null && _ref27 !== void 0 ? _ref27 : fontFamily) !== null && _ref26 !== void 0 ? _ref26 : agentConfig.fontFamily) !== null && _ref25 !== void 0 ? _ref25 : "Inter",
|
|
60253
60363
|
agentMessageBubbleFontSize: (_ref28 = (_ref29 = (_ref30 = agentMessageBubbleFontSize !== null && agentMessageBubbleFontSize !== void 0 ? agentMessageBubbleFontSize : agentConfig.agentMessageBubbleFontSize) !== null && _ref30 !== void 0 ? _ref30 : fontSize) !== null && _ref29 !== void 0 ? _ref29 : agentConfig.fontSize) !== null && _ref28 !== void 0 ? _ref28 : "14px",
|
|
60254
60364
|
userMessageBubbleFontFamily: (_ref31 = (_ref32 = (_ref33 = userMessageBubbleFontFamily !== null && userMessageBubbleFontFamily !== void 0 ? userMessageBubbleFontFamily : agentConfig.userMessageBubbleFontFamily) !== null && _ref33 !== void 0 ? _ref33 : fontFamily) !== null && _ref32 !== void 0 ? _ref32 : agentConfig.fontFamily) !== null && _ref31 !== void 0 ? _ref31 : "Inter",
|
|
@@ -60299,6 +60409,7 @@
|
|
|
60299
60409
|
messages,
|
|
60300
60410
|
inputValue,
|
|
60301
60411
|
isLoading,
|
|
60412
|
+
isConversationLoading,
|
|
60302
60413
|
widgetRef,
|
|
60303
60414
|
setInputValue,
|
|
60304
60415
|
handleSend,
|
|
@@ -60318,7 +60429,8 @@
|
|
|
60318
60429
|
onboardingActive,
|
|
60319
60430
|
onboardingCompleted,
|
|
60320
60431
|
handleOnboardingFormSubmit,
|
|
60321
|
-
conversationId
|
|
60432
|
+
conversationId,
|
|
60433
|
+
resetConversation
|
|
60322
60434
|
} = useChatState(widgetConfig ? {
|
|
60323
60435
|
welcomeMessage: widgetConfig.welcomeMessage,
|
|
60324
60436
|
quickQuestions: widgetConfig.quickQuestions,
|
|
@@ -60331,7 +60443,9 @@
|
|
|
60331
60443
|
enableVoiceInteraction: widgetConfig.enableVoiceInteraction,
|
|
60332
60444
|
onboardingQuestions: widgetConfig.onboardingQuestions,
|
|
60333
60445
|
onboardingEnabled: widgetConfig.onboardingEnabled,
|
|
60334
|
-
collectionPrompt: widgetConfig.collectionPrompt
|
|
60446
|
+
collectionPrompt: widgetConfig.collectionPrompt,
|
|
60447
|
+
inactivityTimeoutValue: widgetConfig.inactivityTimeoutValue,
|
|
60448
|
+
inactivityTimeoutUnit: widgetConfig.inactivityTimeoutUnit
|
|
60335
60449
|
} : defaultConfig);
|
|
60336
60450
|
|
|
60337
60451
|
// Set CSS variables for theming (must be called before early returns)
|
|
@@ -60430,7 +60544,8 @@
|
|
|
60430
60544
|
csatPrompt: widgetConfig.csatPrompt,
|
|
60431
60545
|
csatTriggerType: widgetConfig.csatTriggerType,
|
|
60432
60546
|
csatIdleTimeoutMins: widgetConfig.csatIdleTimeoutMins,
|
|
60433
|
-
onboardingEnabled: widgetConfig.onboardingEnabled
|
|
60547
|
+
onboardingEnabled: widgetConfig.onboardingEnabled,
|
|
60548
|
+
onResetConversation: resetConversation
|
|
60434
60549
|
}), !isOpen && /*#__PURE__*/jsxRuntimeExports.jsx(ToggleButton, {
|
|
60435
60550
|
isOpen: isOpen,
|
|
60436
60551
|
isDark: isDark,
|