playkit-sdk 1.2.12 → 1.3.0
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/LICENSE +86 -86
- package/README.md +246 -244
- package/dist/playkit-sdk.cjs.js +1723 -1530
- package/dist/playkit-sdk.cjs.js.map +1 -1
- package/dist/playkit-sdk.d.ts +66 -5
- package/dist/playkit-sdk.esm.js +1723 -1530
- package/dist/playkit-sdk.esm.js.map +1 -1
- package/dist/playkit-sdk.umd.js +1789 -1571
- package/dist/playkit-sdk.umd.js.map +1 -1
- package/package.json +70 -70
package/dist/playkit-sdk.umd.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* playkit-sdk v1.
|
|
2
|
+
* playkit-sdk v1.3.0
|
|
3
3
|
* PlayKit SDK for JavaScript
|
|
4
4
|
* @license SEE LICENSE IN LICENSE
|
|
5
5
|
*/
|
|
6
6
|
(function (global, factory) {
|
|
7
|
-
typeof exports === 'object' && typeof module !== 'undefined' ? factory(
|
|
8
|
-
typeof define === 'function' && define.amd ? define(
|
|
9
|
-
(global = typeof globalThis !== 'undefined' ? globalThis : global || self,
|
|
10
|
-
})(this, (function (
|
|
7
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
8
|
+
typeof define === 'function' && define.amd ? define(factory) :
|
|
9
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.PlayKitSDK = factory());
|
|
10
|
+
})(this, (function () { 'use strict';
|
|
11
11
|
|
|
12
12
|
function getDefaultExportFromCjs (x) {
|
|
13
13
|
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
|
@@ -467,7 +467,7 @@
|
|
|
467
467
|
* Log level enumeration
|
|
468
468
|
* Lower values = higher priority
|
|
469
469
|
*/
|
|
470
|
-
|
|
470
|
+
var LogLevel;
|
|
471
471
|
(function (LogLevel) {
|
|
472
472
|
/** Disable all logging */
|
|
473
473
|
LogLevel[LogLevel["NONE"] = 0] = "NONE";
|
|
@@ -479,7 +479,7 @@
|
|
|
479
479
|
LogLevel[LogLevel["INFO"] = 3] = "INFO";
|
|
480
480
|
/** Debug level - all logs */
|
|
481
481
|
LogLevel[LogLevel["DEBUG"] = 4] = "DEBUG";
|
|
482
|
-
})(
|
|
482
|
+
})(LogLevel || (LogLevel = {}));
|
|
483
483
|
/**
|
|
484
484
|
* PlayKit Logger
|
|
485
485
|
*
|
|
@@ -603,7 +603,7 @@
|
|
|
603
603
|
* Useful for testing
|
|
604
604
|
*/
|
|
605
605
|
static reset() {
|
|
606
|
-
Logger.globalLevel =
|
|
606
|
+
Logger.globalLevel = LogLevel.WARN;
|
|
607
607
|
Logger.consoleEnabled = true;
|
|
608
608
|
Logger.handlers = [];
|
|
609
609
|
Logger.instances.clear();
|
|
@@ -631,7 +631,7 @@
|
|
|
631
631
|
* @param data Additional data to log
|
|
632
632
|
*/
|
|
633
633
|
debug(message, ...data) {
|
|
634
|
-
this.log(
|
|
634
|
+
this.log(LogLevel.DEBUG, 'debug', message, data);
|
|
635
635
|
}
|
|
636
636
|
/**
|
|
637
637
|
* Log an info message
|
|
@@ -639,7 +639,7 @@
|
|
|
639
639
|
* @param data Additional data to log
|
|
640
640
|
*/
|
|
641
641
|
info(message, ...data) {
|
|
642
|
-
this.log(
|
|
642
|
+
this.log(LogLevel.INFO, 'info', message, data);
|
|
643
643
|
}
|
|
644
644
|
/**
|
|
645
645
|
* Log a warning message
|
|
@@ -647,7 +647,7 @@
|
|
|
647
647
|
* @param data Additional data to log
|
|
648
648
|
*/
|
|
649
649
|
warn(message, ...data) {
|
|
650
|
-
this.log(
|
|
650
|
+
this.log(LogLevel.WARN, 'warn', message, data);
|
|
651
651
|
}
|
|
652
652
|
/**
|
|
653
653
|
* Log an error message
|
|
@@ -655,7 +655,7 @@
|
|
|
655
655
|
* @param data Additional data to log
|
|
656
656
|
*/
|
|
657
657
|
error(message, ...data) {
|
|
658
|
-
this.log(
|
|
658
|
+
this.log(LogLevel.ERROR, 'error', message, data);
|
|
659
659
|
}
|
|
660
660
|
/**
|
|
661
661
|
* Internal log method
|
|
@@ -713,7 +713,7 @@
|
|
|
713
713
|
}
|
|
714
714
|
}
|
|
715
715
|
}
|
|
716
|
-
Logger.globalLevel =
|
|
716
|
+
Logger.globalLevel = LogLevel.WARN;
|
|
717
717
|
Logger.handlers = [];
|
|
718
718
|
Logger.consoleEnabled = true;
|
|
719
719
|
Logger.instances = new Map();
|
|
@@ -1176,6 +1176,15 @@
|
|
|
1176
1176
|
}
|
|
1177
1177
|
}
|
|
1178
1178
|
|
|
1179
|
+
const SDK_TYPE = 'Javascript';
|
|
1180
|
+
const SDK_VERSION = '"1.3.0"';
|
|
1181
|
+
function getSDKHeaders() {
|
|
1182
|
+
return {
|
|
1183
|
+
'X-SDK-Type': SDK_TYPE,
|
|
1184
|
+
'X-SDK-Version': SDK_VERSION,
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1179
1188
|
/**
|
|
1180
1189
|
* Authentication Flow Manager
|
|
1181
1190
|
* Manages the headless authentication flow with automatic UI
|
|
@@ -1280,8 +1289,8 @@
|
|
|
1280
1289
|
constructor(baseURL) {
|
|
1281
1290
|
super();
|
|
1282
1291
|
this.currentSessionId = null;
|
|
1283
|
-
this.
|
|
1284
|
-
this.
|
|
1292
|
+
this._uiContainer = null;
|
|
1293
|
+
this._isSuccess = false;
|
|
1285
1294
|
this.currentLanguage = 'en';
|
|
1286
1295
|
// UI Elements
|
|
1287
1296
|
this.modal = null;
|
|
@@ -1362,84 +1371,84 @@
|
|
|
1362
1371
|
// Create modal container
|
|
1363
1372
|
this.modal = document.createElement('div');
|
|
1364
1373
|
this.modal.className = 'playkit-auth-modal';
|
|
1365
|
-
this.modal.innerHTML = `
|
|
1366
|
-
<div class="playkit-auth-overlay"></div>
|
|
1367
|
-
<div class="playkit-auth-container">
|
|
1368
|
-
<!-- Identifier Panel -->
|
|
1369
|
-
<div class="playkit-auth-panel" id="playkit-identifier-panel">
|
|
1370
|
-
<div class="playkit-auth-header">
|
|
1371
|
-
<h2>${this.t('signIn')}</h2>
|
|
1372
|
-
<p>${this.t('signInSubtitle')}</p>
|
|
1373
|
-
</div>
|
|
1374
|
-
|
|
1375
|
-
<div class="playkit-auth-toggle">
|
|
1376
|
-
<label class="playkit-toggle-option">
|
|
1377
|
-
<input type="radio" name="auth-type" value="email" checked>
|
|
1378
|
-
<span>${this.t('email')}</span>
|
|
1379
|
-
</label>
|
|
1380
|
-
<label class="playkit-toggle-option">
|
|
1381
|
-
<input type="radio" name="auth-type" value="phone">
|
|
1382
|
-
<span>${this.t('phone')}</span>
|
|
1383
|
-
</label>
|
|
1384
|
-
</div>
|
|
1385
|
-
|
|
1386
|
-
<div class="playkit-auth-input-group">
|
|
1387
|
-
<div class="playkit-input-wrapper">
|
|
1388
|
-
<svg class="playkit-input-icon" id="playkit-identifier-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1389
|
-
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
|
|
1390
|
-
<polyline points="22,6 12,13 2,6"></polyline>
|
|
1391
|
-
</svg>
|
|
1392
|
-
<input
|
|
1393
|
-
type="text"
|
|
1394
|
-
id="playkit-identifier-input"
|
|
1395
|
-
placeholder="${this.t('emailPlaceholder')}"
|
|
1396
|
-
autocomplete="off"
|
|
1397
|
-
>
|
|
1398
|
-
</div>
|
|
1399
|
-
</div>
|
|
1400
|
-
|
|
1401
|
-
<button class="playkit-auth-button" id="playkit-send-code-btn">
|
|
1402
|
-
${this.t('sendCode')}
|
|
1403
|
-
</button>
|
|
1404
|
-
|
|
1405
|
-
<div class="playkit-auth-error" id="playkit-error-text"></div>
|
|
1406
|
-
</div>
|
|
1407
|
-
|
|
1408
|
-
<!-- Verification Panel -->
|
|
1409
|
-
<div class="playkit-auth-panel" id="playkit-verification-panel" style="display: none;">
|
|
1410
|
-
<div class="playkit-auth-header">
|
|
1411
|
-
<button class="playkit-back-button" id="playkit-back-btn">
|
|
1412
|
-
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1413
|
-
<path d="M19 12H5M12 19l-7-7 7-7"/>
|
|
1414
|
-
</svg>
|
|
1415
|
-
</button>
|
|
1416
|
-
<h2>${this.t('enterCode')}</h2>
|
|
1417
|
-
<p>${this.t('enterCodeSubtitle')} <span id="playkit-identifier-display"></span></p>
|
|
1418
|
-
</div>
|
|
1419
|
-
|
|
1420
|
-
<div class="playkit-auth-input-group">
|
|
1421
|
-
<div class="playkit-code-inputs">
|
|
1422
|
-
<input type="number" maxlength="1" class="playkit-code-input" data-index="0">
|
|
1423
|
-
<input type="number" maxlength="1" class="playkit-code-input" data-index="1">
|
|
1424
|
-
<input type="number" maxlength="1" class="playkit-code-input" data-index="2">
|
|
1425
|
-
<input type="number" maxlength="1" class="playkit-code-input" data-index="3">
|
|
1426
|
-
<input type="number" maxlength="1" class="playkit-code-input" data-index="4">
|
|
1427
|
-
<input type="number" maxlength="1" class="playkit-code-input" data-index="5">
|
|
1428
|
-
</div>
|
|
1429
|
-
</div>
|
|
1430
|
-
|
|
1431
|
-
<button class="playkit-auth-button" id="playkit-verify-btn">
|
|
1432
|
-
${this.t('verify')}
|
|
1433
|
-
</button>
|
|
1434
|
-
|
|
1435
|
-
<div class="playkit-auth-error" id="playkit-verify-error-text"></div>
|
|
1436
|
-
</div>
|
|
1437
|
-
|
|
1438
|
-
<!-- Loading Overlay -->
|
|
1439
|
-
<div class="playkit-loading-overlay" id="playkit-loading-overlay" style="display: none;">
|
|
1440
|
-
<div class="playkit-spinner"></div>
|
|
1441
|
-
</div>
|
|
1442
|
-
</div>
|
|
1374
|
+
this.modal.innerHTML = `
|
|
1375
|
+
<div class="playkit-auth-overlay"></div>
|
|
1376
|
+
<div class="playkit-auth-container">
|
|
1377
|
+
<!-- Identifier Panel -->
|
|
1378
|
+
<div class="playkit-auth-panel" id="playkit-identifier-panel">
|
|
1379
|
+
<div class="playkit-auth-header">
|
|
1380
|
+
<h2>${this.t('signIn')}</h2>
|
|
1381
|
+
<p>${this.t('signInSubtitle')}</p>
|
|
1382
|
+
</div>
|
|
1383
|
+
|
|
1384
|
+
<div class="playkit-auth-toggle">
|
|
1385
|
+
<label class="playkit-toggle-option">
|
|
1386
|
+
<input type="radio" name="auth-type" value="email" checked>
|
|
1387
|
+
<span>${this.t('email')}</span>
|
|
1388
|
+
</label>
|
|
1389
|
+
<label class="playkit-toggle-option">
|
|
1390
|
+
<input type="radio" name="auth-type" value="phone">
|
|
1391
|
+
<span>${this.t('phone')}</span>
|
|
1392
|
+
</label>
|
|
1393
|
+
</div>
|
|
1394
|
+
|
|
1395
|
+
<div class="playkit-auth-input-group">
|
|
1396
|
+
<div class="playkit-input-wrapper">
|
|
1397
|
+
<svg class="playkit-input-icon" id="playkit-identifier-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1398
|
+
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
|
|
1399
|
+
<polyline points="22,6 12,13 2,6"></polyline>
|
|
1400
|
+
</svg>
|
|
1401
|
+
<input
|
|
1402
|
+
type="text"
|
|
1403
|
+
id="playkit-identifier-input"
|
|
1404
|
+
placeholder="${this.t('emailPlaceholder')}"
|
|
1405
|
+
autocomplete="off"
|
|
1406
|
+
>
|
|
1407
|
+
</div>
|
|
1408
|
+
</div>
|
|
1409
|
+
|
|
1410
|
+
<button class="playkit-auth-button" id="playkit-send-code-btn">
|
|
1411
|
+
${this.t('sendCode')}
|
|
1412
|
+
</button>
|
|
1413
|
+
|
|
1414
|
+
<div class="playkit-auth-error" id="playkit-error-text"></div>
|
|
1415
|
+
</div>
|
|
1416
|
+
|
|
1417
|
+
<!-- Verification Panel -->
|
|
1418
|
+
<div class="playkit-auth-panel" id="playkit-verification-panel" style="display: none;">
|
|
1419
|
+
<div class="playkit-auth-header">
|
|
1420
|
+
<button class="playkit-back-button" id="playkit-back-btn">
|
|
1421
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1422
|
+
<path d="M19 12H5M12 19l-7-7 7-7"/>
|
|
1423
|
+
</svg>
|
|
1424
|
+
</button>
|
|
1425
|
+
<h2>${this.t('enterCode')}</h2>
|
|
1426
|
+
<p>${this.t('enterCodeSubtitle')} <span id="playkit-identifier-display"></span></p>
|
|
1427
|
+
</div>
|
|
1428
|
+
|
|
1429
|
+
<div class="playkit-auth-input-group">
|
|
1430
|
+
<div class="playkit-code-inputs">
|
|
1431
|
+
<input type="number" maxlength="1" class="playkit-code-input" data-index="0">
|
|
1432
|
+
<input type="number" maxlength="1" class="playkit-code-input" data-index="1">
|
|
1433
|
+
<input type="number" maxlength="1" class="playkit-code-input" data-index="2">
|
|
1434
|
+
<input type="number" maxlength="1" class="playkit-code-input" data-index="3">
|
|
1435
|
+
<input type="number" maxlength="1" class="playkit-code-input" data-index="4">
|
|
1436
|
+
<input type="number" maxlength="1" class="playkit-code-input" data-index="5">
|
|
1437
|
+
</div>
|
|
1438
|
+
</div>
|
|
1439
|
+
|
|
1440
|
+
<button class="playkit-auth-button" id="playkit-verify-btn">
|
|
1441
|
+
${this.t('verify')}
|
|
1442
|
+
</button>
|
|
1443
|
+
|
|
1444
|
+
<div class="playkit-auth-error" id="playkit-verify-error-text"></div>
|
|
1445
|
+
</div>
|
|
1446
|
+
|
|
1447
|
+
<!-- Loading Overlay -->
|
|
1448
|
+
<div class="playkit-loading-overlay" id="playkit-loading-overlay" style="display: none;">
|
|
1449
|
+
<div class="playkit-spinner"></div>
|
|
1450
|
+
</div>
|
|
1451
|
+
</div>
|
|
1443
1452
|
`;
|
|
1444
1453
|
// Add styles and load VanillaOTP
|
|
1445
1454
|
this.addStyles();
|
|
@@ -1474,274 +1483,274 @@
|
|
|
1474
1483
|
return;
|
|
1475
1484
|
const style = document.createElement('style');
|
|
1476
1485
|
style.id = styleId;
|
|
1477
|
-
style.textContent = `
|
|
1478
|
-
.playkit-auth-modal {
|
|
1479
|
-
position: fixed;
|
|
1480
|
-
top: 0;
|
|
1481
|
-
left: 0;
|
|
1482
|
-
right: 0;
|
|
1483
|
-
bottom: 0;
|
|
1484
|
-
z-index: 999999;
|
|
1485
|
-
display: flex;
|
|
1486
|
-
justify-content: center;
|
|
1487
|
-
align-items: center;
|
|
1488
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
.playkit-auth-overlay {
|
|
1492
|
-
position: absolute;
|
|
1493
|
-
top: 0;
|
|
1494
|
-
left: 0;
|
|
1495
|
-
right: 0;
|
|
1496
|
-
bottom: 0;
|
|
1497
|
-
background: rgba(0, 0, 0, 0.8);
|
|
1498
|
-
}
|
|
1499
|
-
|
|
1500
|
-
.playkit-auth-container {
|
|
1501
|
-
position: relative;
|
|
1502
|
-
background: #fff;
|
|
1503
|
-
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
1504
|
-
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
|
|
1505
|
-
width: 90%;
|
|
1506
|
-
max-width: 320px;
|
|
1507
|
-
overflow: hidden;
|
|
1508
|
-
}
|
|
1509
|
-
|
|
1510
|
-
.playkit-auth-panel {
|
|
1511
|
-
padding: 24px;
|
|
1512
|
-
}
|
|
1513
|
-
|
|
1514
|
-
.playkit-auth-header {
|
|
1515
|
-
text-align: center;
|
|
1516
|
-
margin-bottom: 20px;
|
|
1517
|
-
position: relative;
|
|
1518
|
-
}
|
|
1519
|
-
|
|
1520
|
-
.playkit-auth-header h2 {
|
|
1521
|
-
margin: 0 0 8px 0;
|
|
1522
|
-
font-size: 14px;
|
|
1523
|
-
font-weight: 600;
|
|
1524
|
-
color: #171717;
|
|
1525
|
-
}
|
|
1526
|
-
|
|
1527
|
-
.playkit-auth-header p {
|
|
1528
|
-
margin: 0;
|
|
1529
|
-
font-size: 14px;
|
|
1530
|
-
color: #666;
|
|
1531
|
-
line-height: 1.5;
|
|
1532
|
-
}
|
|
1533
|
-
|
|
1534
|
-
.playkit-back-button {
|
|
1535
|
-
position: absolute;
|
|
1536
|
-
left: 0;
|
|
1537
|
-
top: 0;
|
|
1538
|
-
background: transparent;
|
|
1539
|
-
border: none;
|
|
1540
|
-
cursor: pointer;
|
|
1541
|
-
padding: 4px;
|
|
1542
|
-
color: #666;
|
|
1543
|
-
transition: background-color 0.2s ease, color 0.2s ease;
|
|
1544
|
-
}
|
|
1545
|
-
|
|
1546
|
-
.playkit-back-button:hover {
|
|
1547
|
-
background: #f5f5f5;
|
|
1548
|
-
color: #171717;
|
|
1549
|
-
}
|
|
1550
|
-
|
|
1551
|
-
.playkit-auth-toggle {
|
|
1552
|
-
display: flex;
|
|
1553
|
-
background: #f5f5f5;
|
|
1554
|
-
padding: 2px;
|
|
1555
|
-
margin-bottom: 20px;
|
|
1556
|
-
gap: 2px;
|
|
1557
|
-
}
|
|
1558
|
-
|
|
1559
|
-
.playkit-toggle-option {
|
|
1560
|
-
flex: 1;
|
|
1561
|
-
display: flex;
|
|
1562
|
-
justify-content: center;
|
|
1563
|
-
align-items: center;
|
|
1564
|
-
padding: 10px 16px;
|
|
1565
|
-
cursor: pointer;
|
|
1566
|
-
transition: background-color 0.2s ease;
|
|
1567
|
-
}
|
|
1568
|
-
|
|
1569
|
-
.playkit-toggle-option input {
|
|
1570
|
-
display: none;
|
|
1571
|
-
}
|
|
1572
|
-
|
|
1573
|
-
.playkit-toggle-option span {
|
|
1574
|
-
font-size: 14px;
|
|
1575
|
-
font-weight: 500;
|
|
1576
|
-
color: #666;
|
|
1577
|
-
transition: color 0.2s ease;
|
|
1578
|
-
}
|
|
1579
|
-
|
|
1580
|
-
.playkit-toggle-option input:checked + span {
|
|
1581
|
-
color: #fff;
|
|
1582
|
-
}
|
|
1583
|
-
|
|
1584
|
-
.playkit-toggle-option:has(input:checked) {
|
|
1585
|
-
background: #171717;
|
|
1586
|
-
}
|
|
1587
|
-
|
|
1588
|
-
.playkit-auth-input-group {
|
|
1589
|
-
margin-bottom: 20px;
|
|
1590
|
-
}
|
|
1591
|
-
|
|
1592
|
-
.playkit-input-wrapper {
|
|
1593
|
-
position: relative;
|
|
1594
|
-
display: flex;
|
|
1595
|
-
align-items: center;
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
|
-
.playkit-input-icon {
|
|
1599
|
-
position: absolute;
|
|
1600
|
-
left: 12px;
|
|
1601
|
-
color: #999;
|
|
1602
|
-
pointer-events: none;
|
|
1603
|
-
}
|
|
1604
|
-
|
|
1605
|
-
.playkit-input-wrapper input {
|
|
1606
|
-
width: 100%;
|
|
1607
|
-
padding: 10px 12px 10px 44px;
|
|
1608
|
-
border: 1px solid #e5e7eb;
|
|
1609
|
-
font-size: 14px;
|
|
1610
|
-
transition: border-color 0.2s ease;
|
|
1611
|
-
box-sizing: border-box;
|
|
1612
|
-
background: #fff;
|
|
1613
|
-
}
|
|
1614
|
-
|
|
1615
|
-
.playkit-input-wrapper input:hover {
|
|
1616
|
-
border-color: #d4d4d4;
|
|
1617
|
-
}
|
|
1618
|
-
|
|
1619
|
-
.playkit-input-wrapper input:focus {
|
|
1620
|
-
outline: none;
|
|
1621
|
-
border-color: #171717;
|
|
1622
|
-
}
|
|
1623
|
-
|
|
1624
|
-
.playkit-code-inputs {
|
|
1625
|
-
display: flex;
|
|
1626
|
-
gap: 8px;
|
|
1627
|
-
justify-content: center;
|
|
1628
|
-
}
|
|
1629
|
-
|
|
1630
|
-
.playkit-code-input {
|
|
1631
|
-
width: 40px !important;
|
|
1632
|
-
height: 48px;
|
|
1633
|
-
text-align: center;
|
|
1634
|
-
font-size: 20px;
|
|
1635
|
-
font-weight: 600;
|
|
1636
|
-
border: 1px solid #e5e7eb !important;
|
|
1637
|
-
padding: 0 !important;
|
|
1638
|
-
transition: border-color 0.2s ease;
|
|
1639
|
-
background: #fff;
|
|
1640
|
-
-moz-appearance: textfield;
|
|
1641
|
-
}
|
|
1642
|
-
|
|
1643
|
-
.playkit-code-input::-webkit-outer-spin-button,
|
|
1644
|
-
.playkit-code-input::-webkit-inner-spin-button {
|
|
1645
|
-
-webkit-appearance: none;
|
|
1646
|
-
margin: 0;
|
|
1647
|
-
}
|
|
1648
|
-
|
|
1649
|
-
.playkit-code-input:hover {
|
|
1650
|
-
border-color: #d4d4d4 !important;
|
|
1651
|
-
}
|
|
1652
|
-
|
|
1653
|
-
.playkit-code-input:focus {
|
|
1654
|
-
outline: none;
|
|
1655
|
-
border-color: #171717 !important;
|
|
1656
|
-
}
|
|
1657
|
-
|
|
1658
|
-
.playkit-auth-button {
|
|
1659
|
-
width: 100%;
|
|
1660
|
-
padding: 10px 16px;
|
|
1661
|
-
background: #171717;
|
|
1662
|
-
color: white;
|
|
1663
|
-
border: none;
|
|
1664
|
-
font-size: 14px;
|
|
1665
|
-
font-weight: 500;
|
|
1666
|
-
cursor: pointer;
|
|
1667
|
-
transition: background 0.2s ease;
|
|
1668
|
-
}
|
|
1669
|
-
|
|
1670
|
-
.playkit-auth-button:hover:not(:disabled) {
|
|
1671
|
-
background: #404040;
|
|
1672
|
-
}
|
|
1673
|
-
|
|
1674
|
-
.playkit-auth-button:active:not(:disabled) {
|
|
1675
|
-
background: #0a0a0a;
|
|
1676
|
-
}
|
|
1677
|
-
|
|
1678
|
-
.playkit-auth-button:disabled {
|
|
1679
|
-
background: #e5e7eb;
|
|
1680
|
-
color: #999;
|
|
1681
|
-
cursor: not-allowed;
|
|
1682
|
-
}
|
|
1683
|
-
|
|
1684
|
-
.playkit-auth-error {
|
|
1685
|
-
margin-top: 16px;
|
|
1686
|
-
padding: 12px 16px;
|
|
1687
|
-
background: #fef2f2;
|
|
1688
|
-
border: 1px solid #fecaca;
|
|
1689
|
-
color: #dc2626;
|
|
1690
|
-
font-size: 13px;
|
|
1691
|
-
text-align: left;
|
|
1692
|
-
display: none;
|
|
1693
|
-
}
|
|
1694
|
-
|
|
1695
|
-
.playkit-auth-error.show {
|
|
1696
|
-
display: block;
|
|
1697
|
-
}
|
|
1698
|
-
|
|
1699
|
-
.playkit-loading-overlay {
|
|
1700
|
-
position: absolute;
|
|
1701
|
-
top: 0;
|
|
1702
|
-
left: 0;
|
|
1703
|
-
right: 0;
|
|
1704
|
-
bottom: 0;
|
|
1705
|
-
background: rgba(255, 255, 255, 0.96);
|
|
1706
|
-
display: flex;
|
|
1707
|
-
justify-content: center;
|
|
1708
|
-
align-items: center;
|
|
1709
|
-
}
|
|
1710
|
-
|
|
1711
|
-
.playkit-spinner {
|
|
1712
|
-
width: 24px;
|
|
1713
|
-
height: 24px;
|
|
1714
|
-
border: 2px solid #e5e7eb;
|
|
1715
|
-
border-top: 2px solid #171717;
|
|
1716
|
-
border-radius: 50%;
|
|
1717
|
-
animation: playkit-spin 1s linear infinite;
|
|
1718
|
-
}
|
|
1719
|
-
|
|
1720
|
-
@keyframes playkit-spin {
|
|
1721
|
-
0% { transform: rotate(0deg); }
|
|
1722
|
-
100% { transform: rotate(360deg); }
|
|
1723
|
-
}
|
|
1724
|
-
|
|
1725
|
-
@media (max-width: 480px) {
|
|
1726
|
-
.playkit-auth-container {
|
|
1727
|
-
width: 95%;
|
|
1728
|
-
max-width: none;
|
|
1729
|
-
}
|
|
1730
|
-
|
|
1731
|
-
.playkit-auth-panel {
|
|
1732
|
-
padding: 20px;
|
|
1733
|
-
}
|
|
1734
|
-
|
|
1735
|
-
.playkit-code-input {
|
|
1736
|
-
width: 36px !important;
|
|
1737
|
-
height: 44px;
|
|
1738
|
-
font-size: 18px;
|
|
1739
|
-
}
|
|
1740
|
-
|
|
1741
|
-
.playkit-code-inputs {
|
|
1742
|
-
gap: 6px;
|
|
1743
|
-
}
|
|
1744
|
-
}
|
|
1486
|
+
style.textContent = `
|
|
1487
|
+
.playkit-auth-modal {
|
|
1488
|
+
position: fixed;
|
|
1489
|
+
top: 0;
|
|
1490
|
+
left: 0;
|
|
1491
|
+
right: 0;
|
|
1492
|
+
bottom: 0;
|
|
1493
|
+
z-index: 999999;
|
|
1494
|
+
display: flex;
|
|
1495
|
+
justify-content: center;
|
|
1496
|
+
align-items: center;
|
|
1497
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
.playkit-auth-overlay {
|
|
1501
|
+
position: absolute;
|
|
1502
|
+
top: 0;
|
|
1503
|
+
left: 0;
|
|
1504
|
+
right: 0;
|
|
1505
|
+
bottom: 0;
|
|
1506
|
+
background: rgba(0, 0, 0, 0.8);
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
.playkit-auth-container {
|
|
1510
|
+
position: relative;
|
|
1511
|
+
background: #fff;
|
|
1512
|
+
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
1513
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
|
|
1514
|
+
width: 90%;
|
|
1515
|
+
max-width: 320px;
|
|
1516
|
+
overflow: hidden;
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
.playkit-auth-panel {
|
|
1520
|
+
padding: 24px;
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
.playkit-auth-header {
|
|
1524
|
+
text-align: center;
|
|
1525
|
+
margin-bottom: 20px;
|
|
1526
|
+
position: relative;
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
.playkit-auth-header h2 {
|
|
1530
|
+
margin: 0 0 8px 0;
|
|
1531
|
+
font-size: 14px;
|
|
1532
|
+
font-weight: 600;
|
|
1533
|
+
color: #171717;
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
.playkit-auth-header p {
|
|
1537
|
+
margin: 0;
|
|
1538
|
+
font-size: 14px;
|
|
1539
|
+
color: #666;
|
|
1540
|
+
line-height: 1.5;
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
.playkit-back-button {
|
|
1544
|
+
position: absolute;
|
|
1545
|
+
left: 0;
|
|
1546
|
+
top: 0;
|
|
1547
|
+
background: transparent;
|
|
1548
|
+
border: none;
|
|
1549
|
+
cursor: pointer;
|
|
1550
|
+
padding: 4px;
|
|
1551
|
+
color: #666;
|
|
1552
|
+
transition: background-color 0.2s ease, color 0.2s ease;
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
.playkit-back-button:hover {
|
|
1556
|
+
background: #f5f5f5;
|
|
1557
|
+
color: #171717;
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
.playkit-auth-toggle {
|
|
1561
|
+
display: flex;
|
|
1562
|
+
background: #f5f5f5;
|
|
1563
|
+
padding: 2px;
|
|
1564
|
+
margin-bottom: 20px;
|
|
1565
|
+
gap: 2px;
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
.playkit-toggle-option {
|
|
1569
|
+
flex: 1;
|
|
1570
|
+
display: flex;
|
|
1571
|
+
justify-content: center;
|
|
1572
|
+
align-items: center;
|
|
1573
|
+
padding: 10px 16px;
|
|
1574
|
+
cursor: pointer;
|
|
1575
|
+
transition: background-color 0.2s ease;
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
.playkit-toggle-option input {
|
|
1579
|
+
display: none;
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
.playkit-toggle-option span {
|
|
1583
|
+
font-size: 14px;
|
|
1584
|
+
font-weight: 500;
|
|
1585
|
+
color: #666;
|
|
1586
|
+
transition: color 0.2s ease;
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
.playkit-toggle-option input:checked + span {
|
|
1590
|
+
color: #fff;
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
.playkit-toggle-option:has(input:checked) {
|
|
1594
|
+
background: #171717;
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
.playkit-auth-input-group {
|
|
1598
|
+
margin-bottom: 20px;
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
.playkit-input-wrapper {
|
|
1602
|
+
position: relative;
|
|
1603
|
+
display: flex;
|
|
1604
|
+
align-items: center;
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
.playkit-input-icon {
|
|
1608
|
+
position: absolute;
|
|
1609
|
+
left: 12px;
|
|
1610
|
+
color: #999;
|
|
1611
|
+
pointer-events: none;
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
.playkit-input-wrapper input {
|
|
1615
|
+
width: 100%;
|
|
1616
|
+
padding: 10px 12px 10px 44px;
|
|
1617
|
+
border: 1px solid #e5e7eb;
|
|
1618
|
+
font-size: 14px;
|
|
1619
|
+
transition: border-color 0.2s ease;
|
|
1620
|
+
box-sizing: border-box;
|
|
1621
|
+
background: #fff;
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
.playkit-input-wrapper input:hover {
|
|
1625
|
+
border-color: #d4d4d4;
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
.playkit-input-wrapper input:focus {
|
|
1629
|
+
outline: none;
|
|
1630
|
+
border-color: #171717;
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
.playkit-code-inputs {
|
|
1634
|
+
display: flex;
|
|
1635
|
+
gap: 8px;
|
|
1636
|
+
justify-content: center;
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
.playkit-code-input {
|
|
1640
|
+
width: 40px !important;
|
|
1641
|
+
height: 48px;
|
|
1642
|
+
text-align: center;
|
|
1643
|
+
font-size: 20px;
|
|
1644
|
+
font-weight: 600;
|
|
1645
|
+
border: 1px solid #e5e7eb !important;
|
|
1646
|
+
padding: 0 !important;
|
|
1647
|
+
transition: border-color 0.2s ease;
|
|
1648
|
+
background: #fff;
|
|
1649
|
+
-moz-appearance: textfield;
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
.playkit-code-input::-webkit-outer-spin-button,
|
|
1653
|
+
.playkit-code-input::-webkit-inner-spin-button {
|
|
1654
|
+
-webkit-appearance: none;
|
|
1655
|
+
margin: 0;
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
.playkit-code-input:hover {
|
|
1659
|
+
border-color: #d4d4d4 !important;
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
.playkit-code-input:focus {
|
|
1663
|
+
outline: none;
|
|
1664
|
+
border-color: #171717 !important;
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
.playkit-auth-button {
|
|
1668
|
+
width: 100%;
|
|
1669
|
+
padding: 10px 16px;
|
|
1670
|
+
background: #171717;
|
|
1671
|
+
color: white;
|
|
1672
|
+
border: none;
|
|
1673
|
+
font-size: 14px;
|
|
1674
|
+
font-weight: 500;
|
|
1675
|
+
cursor: pointer;
|
|
1676
|
+
transition: background 0.2s ease;
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
.playkit-auth-button:hover:not(:disabled) {
|
|
1680
|
+
background: #404040;
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
.playkit-auth-button:active:not(:disabled) {
|
|
1684
|
+
background: #0a0a0a;
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
.playkit-auth-button:disabled {
|
|
1688
|
+
background: #e5e7eb;
|
|
1689
|
+
color: #999;
|
|
1690
|
+
cursor: not-allowed;
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
.playkit-auth-error {
|
|
1694
|
+
margin-top: 16px;
|
|
1695
|
+
padding: 12px 16px;
|
|
1696
|
+
background: #fef2f2;
|
|
1697
|
+
border: 1px solid #fecaca;
|
|
1698
|
+
color: #dc2626;
|
|
1699
|
+
font-size: 13px;
|
|
1700
|
+
text-align: left;
|
|
1701
|
+
display: none;
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
.playkit-auth-error.show {
|
|
1705
|
+
display: block;
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
.playkit-loading-overlay {
|
|
1709
|
+
position: absolute;
|
|
1710
|
+
top: 0;
|
|
1711
|
+
left: 0;
|
|
1712
|
+
right: 0;
|
|
1713
|
+
bottom: 0;
|
|
1714
|
+
background: rgba(255, 255, 255, 0.96);
|
|
1715
|
+
display: flex;
|
|
1716
|
+
justify-content: center;
|
|
1717
|
+
align-items: center;
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
.playkit-spinner {
|
|
1721
|
+
width: 24px;
|
|
1722
|
+
height: 24px;
|
|
1723
|
+
border: 2px solid #e5e7eb;
|
|
1724
|
+
border-top: 2px solid #171717;
|
|
1725
|
+
border-radius: 50%;
|
|
1726
|
+
animation: playkit-spin 1s linear infinite;
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
@keyframes playkit-spin {
|
|
1730
|
+
0% { transform: rotate(0deg); }
|
|
1731
|
+
100% { transform: rotate(360deg); }
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
@media (max-width: 480px) {
|
|
1735
|
+
.playkit-auth-container {
|
|
1736
|
+
width: 95%;
|
|
1737
|
+
max-width: none;
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
.playkit-auth-panel {
|
|
1741
|
+
padding: 20px;
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
.playkit-code-input {
|
|
1745
|
+
width: 36px !important;
|
|
1746
|
+
height: 44px;
|
|
1747
|
+
font-size: 18px;
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
.playkit-code-inputs {
|
|
1751
|
+
gap: 6px;
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1745
1754
|
`;
|
|
1746
1755
|
document.head.appendChild(style);
|
|
1747
1756
|
}
|
|
@@ -1762,14 +1771,14 @@
|
|
|
1762
1771
|
: this.t('phonePlaceholder');
|
|
1763
1772
|
// Update icon
|
|
1764
1773
|
if (isEmail) {
|
|
1765
|
-
identifierIcon.innerHTML = `
|
|
1766
|
-
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
|
|
1767
|
-
<polyline points="22,6 12,13 2,6"></polyline>
|
|
1774
|
+
identifierIcon.innerHTML = `
|
|
1775
|
+
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
|
|
1776
|
+
<polyline points="22,6 12,13 2,6"></polyline>
|
|
1768
1777
|
`;
|
|
1769
1778
|
}
|
|
1770
1779
|
else {
|
|
1771
|
-
identifierIcon.innerHTML = `
|
|
1772
|
-
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path>
|
|
1780
|
+
identifierIcon.innerHTML = `
|
|
1781
|
+
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path>
|
|
1773
1782
|
`;
|
|
1774
1783
|
}
|
|
1775
1784
|
};
|
|
@@ -1790,7 +1799,7 @@
|
|
|
1790
1799
|
this.otpInstance = new window.VanillaOTP(codeInputsContainer);
|
|
1791
1800
|
// Auto-submit when all 6 digits entered
|
|
1792
1801
|
const codeInputs = (_d = this.modal) === null || _d === void 0 ? void 0 : _d.querySelectorAll('.playkit-code-input');
|
|
1793
|
-
codeInputs === null || codeInputs === void 0 ? void 0 : codeInputs.forEach((input,
|
|
1802
|
+
codeInputs === null || codeInputs === void 0 ? void 0 : codeInputs.forEach((input, _index) => {
|
|
1794
1803
|
input.addEventListener('input', () => {
|
|
1795
1804
|
// Check if all inputs are filled
|
|
1796
1805
|
const allFilled = Array.from(codeInputs).every(inp => inp.value.length === 1);
|
|
@@ -1882,7 +1891,7 @@
|
|
|
1882
1891
|
async sendVerificationCode(identifier, type) {
|
|
1883
1892
|
const response = await fetch(`${this.baseURL}/api/auth/send-code`, {
|
|
1884
1893
|
method: 'POST',
|
|
1885
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1894
|
+
headers: Object.assign({ 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
1886
1895
|
body: JSON.stringify({ identifier, type }),
|
|
1887
1896
|
});
|
|
1888
1897
|
if (!response.ok) {
|
|
@@ -1904,7 +1913,7 @@
|
|
|
1904
1913
|
}
|
|
1905
1914
|
const response = await fetch(`${this.baseURL}/api/auth/verify-code`, {
|
|
1906
1915
|
method: 'POST',
|
|
1907
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1916
|
+
headers: Object.assign({ 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
1908
1917
|
body: JSON.stringify({
|
|
1909
1918
|
sessionId: this.currentSessionId,
|
|
1910
1919
|
code,
|
|
@@ -1925,7 +1934,9 @@
|
|
|
1925
1934
|
async setDefaultAuthTypeByRegion() {
|
|
1926
1935
|
var _a;
|
|
1927
1936
|
try {
|
|
1928
|
-
const response = await fetch(`${this.baseURL}/api/reachability
|
|
1937
|
+
const response = await fetch(`${this.baseURL}/api/reachability`, {
|
|
1938
|
+
headers: Object.assign({}, getSDKHeaders()),
|
|
1939
|
+
});
|
|
1929
1940
|
if (response.ok) {
|
|
1930
1941
|
const data = await response.json();
|
|
1931
1942
|
if (data.region === 'CN') {
|
|
@@ -2182,76 +2193,76 @@
|
|
|
2182
2193
|
// Create modal overlay - dark bg-black/80 style
|
|
2183
2194
|
const overlay = document.createElement('div');
|
|
2184
2195
|
overlay.id = 'playkit-login-modal';
|
|
2185
|
-
overlay.style.cssText = `
|
|
2186
|
-
position: fixed;
|
|
2187
|
-
top: 0;
|
|
2188
|
-
left: 0;
|
|
2189
|
-
right: 0;
|
|
2190
|
-
bottom: 0;
|
|
2191
|
-
background: rgba(0, 0, 0, 0.8);
|
|
2192
|
-
display: flex;
|
|
2193
|
-
align-items: center;
|
|
2194
|
-
justify-content: center;
|
|
2195
|
-
z-index: 999999;
|
|
2196
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
2196
|
+
overlay.style.cssText = `
|
|
2197
|
+
position: fixed;
|
|
2198
|
+
top: 0;
|
|
2199
|
+
left: 0;
|
|
2200
|
+
right: 0;
|
|
2201
|
+
bottom: 0;
|
|
2202
|
+
background: rgba(0, 0, 0, 0.8);
|
|
2203
|
+
display: flex;
|
|
2204
|
+
align-items: center;
|
|
2205
|
+
justify-content: center;
|
|
2206
|
+
z-index: 999999;
|
|
2207
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
2197
2208
|
`;
|
|
2198
2209
|
// Create modal card - square corners, shadow-xl style
|
|
2199
2210
|
const card = document.createElement('div');
|
|
2200
|
-
card.style.cssText = `
|
|
2201
|
-
background: #fff;
|
|
2202
|
-
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
2203
|
-
padding: 24px;
|
|
2204
|
-
max-width: 320px;
|
|
2205
|
-
width: 90%;
|
|
2206
|
-
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
|
|
2207
|
-
text-align: center;
|
|
2211
|
+
card.style.cssText = `
|
|
2212
|
+
background: #fff;
|
|
2213
|
+
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
2214
|
+
padding: 24px;
|
|
2215
|
+
max-width: 320px;
|
|
2216
|
+
width: 90%;
|
|
2217
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
|
|
2218
|
+
text-align: center;
|
|
2208
2219
|
`;
|
|
2209
2220
|
// Subtitle / status text
|
|
2210
2221
|
const subtitle = document.createElement('p');
|
|
2211
2222
|
subtitle.id = 'playkit-modal-subtitle';
|
|
2212
2223
|
subtitle.textContent = this.t('loginWithPlayKit');
|
|
2213
|
-
subtitle.style.cssText = `
|
|
2214
|
-
margin: 0 0 20px;
|
|
2215
|
-
font-size: 14px;
|
|
2216
|
-
color: #666;
|
|
2224
|
+
subtitle.style.cssText = `
|
|
2225
|
+
margin: 0 0 20px;
|
|
2226
|
+
font-size: 14px;
|
|
2227
|
+
color: #666;
|
|
2217
2228
|
`;
|
|
2218
2229
|
card.appendChild(subtitle);
|
|
2219
2230
|
// Loading spinner (hidden initially)
|
|
2220
2231
|
const spinner = document.createElement('div');
|
|
2221
2232
|
spinner.id = 'playkit-modal-spinner';
|
|
2222
|
-
spinner.style.cssText = `
|
|
2223
|
-
display: none;
|
|
2224
|
-
width: 24px;
|
|
2225
|
-
height: 24px;
|
|
2226
|
-
margin: 0 auto 16px;
|
|
2227
|
-
border: 2px solid #e5e7eb;
|
|
2228
|
-
border-top-color: #171717;
|
|
2229
|
-
border-radius: 50%;
|
|
2230
|
-
animation: playkit-spin 1s linear infinite;
|
|
2233
|
+
spinner.style.cssText = `
|
|
2234
|
+
display: none;
|
|
2235
|
+
width: 24px;
|
|
2236
|
+
height: 24px;
|
|
2237
|
+
margin: 0 auto 16px;
|
|
2238
|
+
border: 2px solid #e5e7eb;
|
|
2239
|
+
border-top-color: #171717;
|
|
2240
|
+
border-radius: 50%;
|
|
2241
|
+
animation: playkit-spin 1s linear infinite;
|
|
2231
2242
|
`;
|
|
2232
2243
|
card.appendChild(spinner);
|
|
2233
2244
|
// Add keyframes for spinner
|
|
2234
2245
|
const style = document.createElement('style');
|
|
2235
|
-
style.textContent = `
|
|
2236
|
-
@keyframes playkit-spin {
|
|
2237
|
-
to { transform: rotate(360deg); }
|
|
2238
|
-
}
|
|
2246
|
+
style.textContent = `
|
|
2247
|
+
@keyframes playkit-spin {
|
|
2248
|
+
to { transform: rotate(360deg); }
|
|
2249
|
+
}
|
|
2239
2250
|
`;
|
|
2240
2251
|
document.head.appendChild(style);
|
|
2241
2252
|
// Login button - square corners, simple dark style
|
|
2242
2253
|
const loginBtn = document.createElement('button');
|
|
2243
2254
|
loginBtn.id = 'playkit-modal-login-btn';
|
|
2244
2255
|
loginBtn.textContent = this.t('loginToPlay');
|
|
2245
|
-
loginBtn.style.cssText = `
|
|
2246
|
-
width: 100%;
|
|
2247
|
-
padding: 10px 16px;
|
|
2248
|
-
font-size: 14px;
|
|
2249
|
-
font-weight: 500;
|
|
2250
|
-
color: white;
|
|
2251
|
-
background: #171717;
|
|
2252
|
-
border: none;
|
|
2253
|
-
cursor: pointer;
|
|
2254
|
-
transition: background 0.2s ease;
|
|
2256
|
+
loginBtn.style.cssText = `
|
|
2257
|
+
width: 100%;
|
|
2258
|
+
padding: 10px 16px;
|
|
2259
|
+
font-size: 14px;
|
|
2260
|
+
font-weight: 500;
|
|
2261
|
+
color: white;
|
|
2262
|
+
background: #171717;
|
|
2263
|
+
border: none;
|
|
2264
|
+
cursor: pointer;
|
|
2265
|
+
transition: background 0.2s ease;
|
|
2255
2266
|
`;
|
|
2256
2267
|
loginBtn.onmouseenter = () => {
|
|
2257
2268
|
loginBtn.style.background = '#404040';
|
|
@@ -2278,18 +2289,18 @@
|
|
|
2278
2289
|
const cancelBtn = document.createElement('button');
|
|
2279
2290
|
cancelBtn.id = 'playkit-modal-cancel-btn';
|
|
2280
2291
|
cancelBtn.textContent = this.t('cancel');
|
|
2281
|
-
cancelBtn.style.cssText = `
|
|
2282
|
-
display: none;
|
|
2283
|
-
width: 100%;
|
|
2284
|
-
margin-top: 8px;
|
|
2285
|
-
padding: 10px 16px;
|
|
2286
|
-
font-size: 14px;
|
|
2287
|
-
font-weight: 500;
|
|
2288
|
-
color: #666;
|
|
2289
|
-
background: transparent;
|
|
2290
|
-
border: 1px solid #e5e7eb;
|
|
2291
|
-
cursor: pointer;
|
|
2292
|
-
transition: all 0.2s ease;
|
|
2292
|
+
cancelBtn.style.cssText = `
|
|
2293
|
+
display: none;
|
|
2294
|
+
width: 100%;
|
|
2295
|
+
margin-top: 8px;
|
|
2296
|
+
padding: 10px 16px;
|
|
2297
|
+
font-size: 14px;
|
|
2298
|
+
font-weight: 500;
|
|
2299
|
+
color: #666;
|
|
2300
|
+
background: transparent;
|
|
2301
|
+
border: 1px solid #e5e7eb;
|
|
2302
|
+
cursor: pointer;
|
|
2303
|
+
transition: all 0.2s ease;
|
|
2293
2304
|
`;
|
|
2294
2305
|
cancelBtn.onmouseenter = () => {
|
|
2295
2306
|
cancelBtn.style.background = '#f5f5f5';
|
|
@@ -2359,11 +2370,11 @@
|
|
|
2359
2370
|
// Create error title
|
|
2360
2371
|
const errorTitle = document.createElement('h3');
|
|
2361
2372
|
errorTitle.textContent = this.t(titleKey);
|
|
2362
|
-
errorTitle.style.cssText = `
|
|
2363
|
-
margin: 0 0 8px;
|
|
2364
|
-
font-size: 14px;
|
|
2365
|
-
font-weight: 600;
|
|
2366
|
-
color: ${iconColor};
|
|
2373
|
+
errorTitle.style.cssText = `
|
|
2374
|
+
margin: 0 0 8px;
|
|
2375
|
+
font-size: 14px;
|
|
2376
|
+
font-weight: 600;
|
|
2377
|
+
color: ${iconColor};
|
|
2367
2378
|
`;
|
|
2368
2379
|
// Update subtitle with error description
|
|
2369
2380
|
subtitle.textContent = this.t(descKey);
|
|
@@ -2433,9 +2444,7 @@
|
|
|
2433
2444
|
// Step 1: Initiate device auth session
|
|
2434
2445
|
const initResponse = await fetch(`${this.baseURL}/api/device-auth/initiate`, {
|
|
2435
2446
|
method: 'POST',
|
|
2436
|
-
headers: {
|
|
2437
|
-
'Content-Type': 'application/json',
|
|
2438
|
-
},
|
|
2447
|
+
headers: Object.assign({ 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
2439
2448
|
body: JSON.stringify({
|
|
2440
2449
|
game_id: this.gameId,
|
|
2441
2450
|
code_challenge: codeChallenge,
|
|
@@ -2504,7 +2513,7 @@
|
|
|
2504
2513
|
return;
|
|
2505
2514
|
}
|
|
2506
2515
|
try {
|
|
2507
|
-
const pollResponse = await fetch(`${this.baseURL}/api/device-auth/poll?session_id=${encodeURIComponent(session_id)}&code_verifier=${encodeURIComponent(codeVerifier)}
|
|
2516
|
+
const pollResponse = await fetch(`${this.baseURL}/api/device-auth/poll?session_id=${encodeURIComponent(session_id)}&code_verifier=${encodeURIComponent(codeVerifier)}`, { headers: Object.assign({}, getSDKHeaders()) });
|
|
2508
2517
|
const pollData = await pollResponse.json();
|
|
2509
2518
|
if (pollResponse.ok) {
|
|
2510
2519
|
if (pollData.status === 'pending') {
|
|
@@ -2632,9 +2641,7 @@
|
|
|
2632
2641
|
// Initiate device auth session
|
|
2633
2642
|
const initResponse = await fetch(`${this.baseURL}/api/device-auth/initiate`, {
|
|
2634
2643
|
method: 'POST',
|
|
2635
|
-
headers: {
|
|
2636
|
-
'Content-Type': 'application/json',
|
|
2637
|
-
},
|
|
2644
|
+
headers: Object.assign({ 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
2638
2645
|
body: JSON.stringify({
|
|
2639
2646
|
game_id: this.gameId,
|
|
2640
2647
|
code_challenge: codeChallenge,
|
|
@@ -2697,7 +2704,7 @@
|
|
|
2697
2704
|
return;
|
|
2698
2705
|
}
|
|
2699
2706
|
try {
|
|
2700
|
-
const pollResponse = await fetch(`${this.baseURL}/api/device-auth/poll?session_id=${encodeURIComponent(sessionId)}&code_verifier=${encodeURIComponent(codeVerifier)}
|
|
2707
|
+
const pollResponse = await fetch(`${this.baseURL}/api/device-auth/poll?session_id=${encodeURIComponent(sessionId)}&code_verifier=${encodeURIComponent(codeVerifier)}`, { headers: Object.assign({}, getSDKHeaders()) });
|
|
2701
2708
|
const pollData = await pollResponse.json();
|
|
2702
2709
|
if (pollResponse.ok) {
|
|
2703
2710
|
if (pollData.status === 'pending') {
|
|
@@ -2777,6 +2784,8 @@
|
|
|
2777
2784
|
this.logger = Logger.getLogger('AuthManager');
|
|
2778
2785
|
/** Shared promise for current device auth flow - allows multiple callers to await the same result */
|
|
2779
2786
|
this.currentDeviceAuthFlowPromise = null;
|
|
2787
|
+
/** Shared promise for current auth flow (startAuthFlow) - allows multiple callers to await the same result */
|
|
2788
|
+
this.currentAuthFlowPromise = null;
|
|
2780
2789
|
this.config = config;
|
|
2781
2790
|
// Create TokenStorage with appropriate mode for server vs browser environment
|
|
2782
2791
|
this.storage = new TokenStorage({
|
|
@@ -2885,12 +2894,27 @@
|
|
|
2885
2894
|
* @deprecated 'headless' authentication is deprecated and will be removed in v2.0. Use 'device' instead.
|
|
2886
2895
|
*/
|
|
2887
2896
|
async startAuthFlow(authMethod = 'device') {
|
|
2888
|
-
|
|
2889
|
-
if (this.
|
|
2890
|
-
|
|
2891
|
-
this.
|
|
2892
|
-
|
|
2897
|
+
// If a flow is already in progress, return the shared promise so all callers await the same result
|
|
2898
|
+
if (this.currentAuthFlowPromise) {
|
|
2899
|
+
this.logger.debug('Auth flow already in progress, waiting for existing flow');
|
|
2900
|
+
return this.currentAuthFlowPromise;
|
|
2901
|
+
}
|
|
2902
|
+
// Store the flow promise so subsequent calls can await the same result
|
|
2903
|
+
const flowPromise = this.executeAuthFlow(authMethod);
|
|
2904
|
+
this.currentAuthFlowPromise = flowPromise;
|
|
2905
|
+
try {
|
|
2906
|
+
return await flowPromise;
|
|
2907
|
+
}
|
|
2908
|
+
finally {
|
|
2909
|
+
this.currentAuthFlowPromise = null;
|
|
2893
2910
|
}
|
|
2911
|
+
}
|
|
2912
|
+
/**
|
|
2913
|
+
* Internal method that executes the actual auth flow
|
|
2914
|
+
* @private
|
|
2915
|
+
*/
|
|
2916
|
+
async executeAuthFlow(authMethod = 'device') {
|
|
2917
|
+
var _a, _b;
|
|
2894
2918
|
// Deprecation warning for headless auth
|
|
2895
2919
|
if (authMethod === 'headless') {
|
|
2896
2920
|
this.logger.warn('"headless" authentication is deprecated and will be removed in v2.0. ' +
|
|
@@ -2949,10 +2973,7 @@
|
|
|
2949
2973
|
try {
|
|
2950
2974
|
const response = await fetch(`${this.baseURL}${JWT_EXCHANGE_ENDPOINT}`, {
|
|
2951
2975
|
method: 'POST',
|
|
2952
|
-
headers: {
|
|
2953
|
-
Authorization: `Bearer ${jwt}`,
|
|
2954
|
-
'Content-Type': 'application/json',
|
|
2955
|
-
},
|
|
2976
|
+
headers: Object.assign({ Authorization: `Bearer ${jwt}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
2956
2977
|
body: JSON.stringify({ gameId: this.config.gameId }),
|
|
2957
2978
|
});
|
|
2958
2979
|
if (!response.ok) {
|
|
@@ -3302,9 +3323,7 @@
|
|
|
3302
3323
|
this.logger.debug('Refreshing access token');
|
|
3303
3324
|
const response = await fetch(`${this.baseURL}${TOKEN_REFRESH_ENDPOINT}`, {
|
|
3304
3325
|
method: 'POST',
|
|
3305
|
-
headers: {
|
|
3306
|
-
'Content-Type': 'application/json',
|
|
3307
|
-
},
|
|
3326
|
+
headers: Object.assign({ 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
3308
3327
|
body: JSON.stringify({
|
|
3309
3328
|
refresh_token: this.authState.refreshToken,
|
|
3310
3329
|
}),
|
|
@@ -3407,7 +3426,7 @@
|
|
|
3407
3426
|
* RechargeManager handles the recharge modal UI and recharge window opening
|
|
3408
3427
|
*/
|
|
3409
3428
|
class RechargeManager extends EventEmitter {
|
|
3410
|
-
constructor(playerToken, rechargePortalUrl = 'https://playkit.ai/recharge', gameId) {
|
|
3429
|
+
constructor(playerToken, rechargePortalUrl = 'https://players.playkit.ai/recharge', gameId) {
|
|
3411
3430
|
super();
|
|
3412
3431
|
this.modalContainer = null;
|
|
3413
3432
|
this.styleElement = null;
|
|
@@ -3510,220 +3529,220 @@
|
|
|
3510
3529
|
return;
|
|
3511
3530
|
}
|
|
3512
3531
|
this.styleElement = document.createElement('style');
|
|
3513
|
-
this.styleElement.textContent = `
|
|
3514
|
-
.playkit-recharge-overlay {
|
|
3515
|
-
position: fixed;
|
|
3516
|
-
top: 0;
|
|
3517
|
-
left: 0;
|
|
3518
|
-
right: 0;
|
|
3519
|
-
bottom: 0;
|
|
3520
|
-
background: rgba(0, 0, 0, 0.8);
|
|
3521
|
-
display: flex;
|
|
3522
|
-
justify-content: center;
|
|
3523
|
-
align-items: center;
|
|
3524
|
-
z-index: 999999;
|
|
3525
|
-
animation: playkit-recharge-fadeIn 0.2s ease-out;
|
|
3526
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3527
|
-
}
|
|
3528
|
-
|
|
3529
|
-
@keyframes playkit-recharge-fadeIn {
|
|
3530
|
-
from {
|
|
3531
|
-
opacity: 0;
|
|
3532
|
-
}
|
|
3533
|
-
to {
|
|
3534
|
-
opacity: 1;
|
|
3535
|
-
}
|
|
3536
|
-
}
|
|
3537
|
-
|
|
3538
|
-
.playkit-recharge-modal {
|
|
3539
|
-
background: #fff;
|
|
3540
|
-
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
3541
|
-
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
|
|
3542
|
-
padding: 24px;
|
|
3543
|
-
max-width: 320px;
|
|
3544
|
-
width: 90%;
|
|
3545
|
-
position: relative;
|
|
3546
|
-
text-align: center;
|
|
3547
|
-
}
|
|
3548
|
-
|
|
3549
|
-
.playkit-recharge-title {
|
|
3550
|
-
font-size: 14px;
|
|
3551
|
-
font-weight: 600;
|
|
3552
|
-
color: #171717;
|
|
3553
|
-
margin: 0 0 8px 0;
|
|
3554
|
-
text-align: center;
|
|
3555
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3556
|
-
}
|
|
3557
|
-
|
|
3558
|
-
.playkit-recharge-message {
|
|
3559
|
-
font-size: 14px;
|
|
3560
|
-
color: #666;
|
|
3561
|
-
margin: 0 0 20px 0;
|
|
3562
|
-
text-align: center;
|
|
3563
|
-
line-height: 1.5;
|
|
3564
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3565
|
-
}
|
|
3566
|
-
|
|
3567
|
-
.playkit-recharge-balance {
|
|
3568
|
-
background: #f5f5f5;
|
|
3569
|
-
border: 1px solid #e5e7eb;
|
|
3570
|
-
padding: 16px;
|
|
3571
|
-
margin: 0 0 20px 0;
|
|
3572
|
-
text-align: center;
|
|
3573
|
-
}
|
|
3574
|
-
|
|
3575
|
-
.playkit-recharge-balance-label {
|
|
3576
|
-
font-size: 12px;
|
|
3577
|
-
color: #666;
|
|
3578
|
-
margin: 0 0 8px 0;
|
|
3579
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3580
|
-
}
|
|
3581
|
-
|
|
3582
|
-
.playkit-recharge-balance-value {
|
|
3583
|
-
font-size: 24px;
|
|
3584
|
-
font-weight: bold;
|
|
3585
|
-
color: #171717;
|
|
3586
|
-
margin: 0;
|
|
3587
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3588
|
-
}
|
|
3589
|
-
|
|
3590
|
-
.playkit-recharge-balance-unit {
|
|
3591
|
-
font-size: 14px;
|
|
3592
|
-
color: #666;
|
|
3593
|
-
margin-left: 4px;
|
|
3594
|
-
}
|
|
3595
|
-
|
|
3596
|
-
.playkit-recharge-buttons {
|
|
3597
|
-
display: flex;
|
|
3598
|
-
flex-direction: column;
|
|
3599
|
-
gap: 8px;
|
|
3600
|
-
}
|
|
3601
|
-
|
|
3602
|
-
.playkit-recharge-button {
|
|
3603
|
-
width: 100%;
|
|
3604
|
-
padding: 10px 16px;
|
|
3605
|
-
border: none;
|
|
3606
|
-
font-size: 14px;
|
|
3607
|
-
font-weight: 500;
|
|
3608
|
-
cursor: pointer;
|
|
3609
|
-
transition: all 0.2s ease;
|
|
3610
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3611
|
-
}
|
|
3612
|
-
|
|
3613
|
-
.playkit-recharge-button-primary {
|
|
3614
|
-
background: #171717;
|
|
3615
|
-
color: white;
|
|
3616
|
-
}
|
|
3617
|
-
|
|
3618
|
-
.playkit-recharge-button-primary:hover {
|
|
3619
|
-
background: #404040;
|
|
3620
|
-
}
|
|
3621
|
-
|
|
3622
|
-
.playkit-recharge-button-primary:active {
|
|
3623
|
-
background: #0a0a0a;
|
|
3624
|
-
}
|
|
3625
|
-
|
|
3626
|
-
.playkit-recharge-button-secondary {
|
|
3627
|
-
background: transparent;
|
|
3628
|
-
color: #666;
|
|
3629
|
-
border: 1px solid #e5e7eb;
|
|
3630
|
-
}
|
|
3631
|
-
|
|
3632
|
-
.playkit-recharge-button-secondary:hover {
|
|
3633
|
-
background: #f5f5f5;
|
|
3634
|
-
border-color: #d4d4d4;
|
|
3635
|
-
}
|
|
3636
|
-
|
|
3637
|
-
.playkit-recharge-button-secondary:active {
|
|
3638
|
-
background: #e5e5e5;
|
|
3639
|
-
}
|
|
3640
|
-
|
|
3641
|
-
@media (max-width: 480px) {
|
|
3642
|
-
.playkit-recharge-modal {
|
|
3643
|
-
padding: 20px;
|
|
3644
|
-
}
|
|
3645
|
-
}
|
|
3646
|
-
|
|
3647
|
-
/* Daily Refresh Toast Styles */
|
|
3648
|
-
.playkit-daily-refresh-toast {
|
|
3649
|
-
position: fixed;
|
|
3650
|
-
top: 20px;
|
|
3651
|
-
right: 20px;
|
|
3652
|
-
background: #fff;
|
|
3653
|
-
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
3654
|
-
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.1);
|
|
3655
|
-
padding: 16px 20px;
|
|
3656
|
-
min-width: 240px;
|
|
3657
|
-
max-width: 320px;
|
|
3658
|
-
z-index: 999998;
|
|
3659
|
-
animation: playkit-toast-slideIn 0.3s ease-out;
|
|
3660
|
-
display: flex;
|
|
3661
|
-
align-items: flex-start;
|
|
3662
|
-
gap: 12px;
|
|
3663
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3664
|
-
}
|
|
3665
|
-
|
|
3666
|
-
.playkit-daily-refresh-toast.hiding {
|
|
3667
|
-
animation: playkit-toast-fadeOut 0.3s ease-out forwards;
|
|
3668
|
-
}
|
|
3669
|
-
|
|
3670
|
-
@keyframes playkit-toast-slideIn {
|
|
3671
|
-
from {
|
|
3672
|
-
transform: translateX(100%);
|
|
3673
|
-
opacity: 0;
|
|
3674
|
-
}
|
|
3675
|
-
to {
|
|
3676
|
-
transform: translateX(0);
|
|
3677
|
-
opacity: 1;
|
|
3678
|
-
}
|
|
3679
|
-
}
|
|
3680
|
-
|
|
3681
|
-
@keyframes playkit-toast-fadeOut {
|
|
3682
|
-
from {
|
|
3683
|
-
transform: translateX(0);
|
|
3684
|
-
opacity: 1;
|
|
3685
|
-
}
|
|
3686
|
-
to {
|
|
3687
|
-
transform: translateX(100%);
|
|
3688
|
-
opacity: 0;
|
|
3689
|
-
}
|
|
3690
|
-
}
|
|
3691
|
-
|
|
3692
|
-
.playkit-toast-icon {
|
|
3693
|
-
width: 24px;
|
|
3694
|
-
height: 24px;
|
|
3695
|
-
background: #171717;
|
|
3696
|
-
border-radius: 50%;
|
|
3697
|
-
display: flex;
|
|
3698
|
-
align-items: center;
|
|
3699
|
-
justify-content: center;
|
|
3700
|
-
flex-shrink: 0;
|
|
3701
|
-
}
|
|
3702
|
-
|
|
3703
|
-
.playkit-toast-icon svg {
|
|
3704
|
-
width: 14px;
|
|
3705
|
-
height: 14px;
|
|
3706
|
-
color: #ffffff;
|
|
3707
|
-
}
|
|
3708
|
-
|
|
3709
|
-
.playkit-toast-message {
|
|
3710
|
-
flex: 1;
|
|
3711
|
-
font-size: 14px;
|
|
3712
|
-
font-weight: 500;
|
|
3713
|
-
color: #171717;
|
|
3714
|
-
line-height: 1.4;
|
|
3715
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3716
|
-
}
|
|
3717
|
-
|
|
3718
|
-
@media (max-width: 480px) {
|
|
3719
|
-
.playkit-daily-refresh-toast {
|
|
3720
|
-
top: 10px;
|
|
3721
|
-
right: 10px;
|
|
3722
|
-
left: 10px;
|
|
3723
|
-
min-width: auto;
|
|
3724
|
-
max-width: none;
|
|
3725
|
-
}
|
|
3726
|
-
}
|
|
3532
|
+
this.styleElement.textContent = `
|
|
3533
|
+
.playkit-recharge-overlay {
|
|
3534
|
+
position: fixed;
|
|
3535
|
+
top: 0;
|
|
3536
|
+
left: 0;
|
|
3537
|
+
right: 0;
|
|
3538
|
+
bottom: 0;
|
|
3539
|
+
background: rgba(0, 0, 0, 0.8);
|
|
3540
|
+
display: flex;
|
|
3541
|
+
justify-content: center;
|
|
3542
|
+
align-items: center;
|
|
3543
|
+
z-index: 999999;
|
|
3544
|
+
animation: playkit-recharge-fadeIn 0.2s ease-out;
|
|
3545
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3546
|
+
}
|
|
3547
|
+
|
|
3548
|
+
@keyframes playkit-recharge-fadeIn {
|
|
3549
|
+
from {
|
|
3550
|
+
opacity: 0;
|
|
3551
|
+
}
|
|
3552
|
+
to {
|
|
3553
|
+
opacity: 1;
|
|
3554
|
+
}
|
|
3555
|
+
}
|
|
3556
|
+
|
|
3557
|
+
.playkit-recharge-modal {
|
|
3558
|
+
background: #fff;
|
|
3559
|
+
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
3560
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
|
|
3561
|
+
padding: 24px;
|
|
3562
|
+
max-width: 320px;
|
|
3563
|
+
width: 90%;
|
|
3564
|
+
position: relative;
|
|
3565
|
+
text-align: center;
|
|
3566
|
+
}
|
|
3567
|
+
|
|
3568
|
+
.playkit-recharge-title {
|
|
3569
|
+
font-size: 14px;
|
|
3570
|
+
font-weight: 600;
|
|
3571
|
+
color: #171717;
|
|
3572
|
+
margin: 0 0 8px 0;
|
|
3573
|
+
text-align: center;
|
|
3574
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3575
|
+
}
|
|
3576
|
+
|
|
3577
|
+
.playkit-recharge-message {
|
|
3578
|
+
font-size: 14px;
|
|
3579
|
+
color: #666;
|
|
3580
|
+
margin: 0 0 20px 0;
|
|
3581
|
+
text-align: center;
|
|
3582
|
+
line-height: 1.5;
|
|
3583
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3584
|
+
}
|
|
3585
|
+
|
|
3586
|
+
.playkit-recharge-balance {
|
|
3587
|
+
background: #f5f5f5;
|
|
3588
|
+
border: 1px solid #e5e7eb;
|
|
3589
|
+
padding: 16px;
|
|
3590
|
+
margin: 0 0 20px 0;
|
|
3591
|
+
text-align: center;
|
|
3592
|
+
}
|
|
3593
|
+
|
|
3594
|
+
.playkit-recharge-balance-label {
|
|
3595
|
+
font-size: 12px;
|
|
3596
|
+
color: #666;
|
|
3597
|
+
margin: 0 0 8px 0;
|
|
3598
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3599
|
+
}
|
|
3600
|
+
|
|
3601
|
+
.playkit-recharge-balance-value {
|
|
3602
|
+
font-size: 24px;
|
|
3603
|
+
font-weight: bold;
|
|
3604
|
+
color: #171717;
|
|
3605
|
+
margin: 0;
|
|
3606
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3607
|
+
}
|
|
3608
|
+
|
|
3609
|
+
.playkit-recharge-balance-unit {
|
|
3610
|
+
font-size: 14px;
|
|
3611
|
+
color: #666;
|
|
3612
|
+
margin-left: 4px;
|
|
3613
|
+
}
|
|
3614
|
+
|
|
3615
|
+
.playkit-recharge-buttons {
|
|
3616
|
+
display: flex;
|
|
3617
|
+
flex-direction: column;
|
|
3618
|
+
gap: 8px;
|
|
3619
|
+
}
|
|
3620
|
+
|
|
3621
|
+
.playkit-recharge-button {
|
|
3622
|
+
width: 100%;
|
|
3623
|
+
padding: 10px 16px;
|
|
3624
|
+
border: none;
|
|
3625
|
+
font-size: 14px;
|
|
3626
|
+
font-weight: 500;
|
|
3627
|
+
cursor: pointer;
|
|
3628
|
+
transition: all 0.2s ease;
|
|
3629
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3630
|
+
}
|
|
3631
|
+
|
|
3632
|
+
.playkit-recharge-button-primary {
|
|
3633
|
+
background: #171717;
|
|
3634
|
+
color: white;
|
|
3635
|
+
}
|
|
3636
|
+
|
|
3637
|
+
.playkit-recharge-button-primary:hover {
|
|
3638
|
+
background: #404040;
|
|
3639
|
+
}
|
|
3640
|
+
|
|
3641
|
+
.playkit-recharge-button-primary:active {
|
|
3642
|
+
background: #0a0a0a;
|
|
3643
|
+
}
|
|
3644
|
+
|
|
3645
|
+
.playkit-recharge-button-secondary {
|
|
3646
|
+
background: transparent;
|
|
3647
|
+
color: #666;
|
|
3648
|
+
border: 1px solid #e5e7eb;
|
|
3649
|
+
}
|
|
3650
|
+
|
|
3651
|
+
.playkit-recharge-button-secondary:hover {
|
|
3652
|
+
background: #f5f5f5;
|
|
3653
|
+
border-color: #d4d4d4;
|
|
3654
|
+
}
|
|
3655
|
+
|
|
3656
|
+
.playkit-recharge-button-secondary:active {
|
|
3657
|
+
background: #e5e5e5;
|
|
3658
|
+
}
|
|
3659
|
+
|
|
3660
|
+
@media (max-width: 480px) {
|
|
3661
|
+
.playkit-recharge-modal {
|
|
3662
|
+
padding: 20px;
|
|
3663
|
+
}
|
|
3664
|
+
}
|
|
3665
|
+
|
|
3666
|
+
/* Daily Refresh Toast Styles */
|
|
3667
|
+
.playkit-daily-refresh-toast {
|
|
3668
|
+
position: fixed;
|
|
3669
|
+
top: 20px;
|
|
3670
|
+
right: 20px;
|
|
3671
|
+
background: #fff;
|
|
3672
|
+
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
3673
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.1);
|
|
3674
|
+
padding: 16px 20px;
|
|
3675
|
+
min-width: 240px;
|
|
3676
|
+
max-width: 320px;
|
|
3677
|
+
z-index: 999998;
|
|
3678
|
+
animation: playkit-toast-slideIn 0.3s ease-out;
|
|
3679
|
+
display: flex;
|
|
3680
|
+
align-items: flex-start;
|
|
3681
|
+
gap: 12px;
|
|
3682
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3683
|
+
}
|
|
3684
|
+
|
|
3685
|
+
.playkit-daily-refresh-toast.hiding {
|
|
3686
|
+
animation: playkit-toast-fadeOut 0.3s ease-out forwards;
|
|
3687
|
+
}
|
|
3688
|
+
|
|
3689
|
+
@keyframes playkit-toast-slideIn {
|
|
3690
|
+
from {
|
|
3691
|
+
transform: translateX(100%);
|
|
3692
|
+
opacity: 0;
|
|
3693
|
+
}
|
|
3694
|
+
to {
|
|
3695
|
+
transform: translateX(0);
|
|
3696
|
+
opacity: 1;
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
3699
|
+
|
|
3700
|
+
@keyframes playkit-toast-fadeOut {
|
|
3701
|
+
from {
|
|
3702
|
+
transform: translateX(0);
|
|
3703
|
+
opacity: 1;
|
|
3704
|
+
}
|
|
3705
|
+
to {
|
|
3706
|
+
transform: translateX(100%);
|
|
3707
|
+
opacity: 0;
|
|
3708
|
+
}
|
|
3709
|
+
}
|
|
3710
|
+
|
|
3711
|
+
.playkit-toast-icon {
|
|
3712
|
+
width: 24px;
|
|
3713
|
+
height: 24px;
|
|
3714
|
+
background: #171717;
|
|
3715
|
+
border-radius: 50%;
|
|
3716
|
+
display: flex;
|
|
3717
|
+
align-items: center;
|
|
3718
|
+
justify-content: center;
|
|
3719
|
+
flex-shrink: 0;
|
|
3720
|
+
}
|
|
3721
|
+
|
|
3722
|
+
.playkit-toast-icon svg {
|
|
3723
|
+
width: 14px;
|
|
3724
|
+
height: 14px;
|
|
3725
|
+
color: #ffffff;
|
|
3726
|
+
}
|
|
3727
|
+
|
|
3728
|
+
.playkit-toast-message {
|
|
3729
|
+
flex: 1;
|
|
3730
|
+
font-size: 14px;
|
|
3731
|
+
font-weight: 500;
|
|
3732
|
+
color: #171717;
|
|
3733
|
+
line-height: 1.4;
|
|
3734
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3735
|
+
}
|
|
3736
|
+
|
|
3737
|
+
@media (max-width: 480px) {
|
|
3738
|
+
.playkit-daily-refresh-toast {
|
|
3739
|
+
top: 10px;
|
|
3740
|
+
right: 10px;
|
|
3741
|
+
left: 10px;
|
|
3742
|
+
min-width: auto;
|
|
3743
|
+
max-width: none;
|
|
3744
|
+
}
|
|
3745
|
+
}
|
|
3727
3746
|
`;
|
|
3728
3747
|
document.head.appendChild(this.styleElement);
|
|
3729
3748
|
}
|
|
@@ -3860,7 +3879,8 @@
|
|
|
3860
3879
|
/**
|
|
3861
3880
|
* Player client for managing player information and credits
|
|
3862
3881
|
*/
|
|
3863
|
-
|
|
3882
|
+
// @ts-ignore - replaced at build time
|
|
3883
|
+
const DEFAULT_BASE_URL$4 = "https://api.playkit.ai";
|
|
3864
3884
|
const PLAYER_INFO_ENDPOINT = '/api/external/player-info';
|
|
3865
3885
|
const SET_NICKNAME_ENDPOINT = '/api/external/set-game-player-nickname';
|
|
3866
3886
|
class PlayerClient extends EventEmitter {
|
|
@@ -3878,7 +3898,7 @@
|
|
|
3878
3898
|
autoShowBalanceModal: (_a = rechargeConfig.autoShowBalanceModal) !== null && _a !== void 0 ? _a : true,
|
|
3879
3899
|
balanceCheckInterval: (_b = rechargeConfig.balanceCheckInterval) !== null && _b !== void 0 ? _b : 30000,
|
|
3880
3900
|
checkBalanceAfterApiCall: (_c = rechargeConfig.checkBalanceAfterApiCall) !== null && _c !== void 0 ? _c : true,
|
|
3881
|
-
rechargePortalUrl: rechargeConfig.rechargePortalUrl || 'https://playkit.ai/recharge',
|
|
3901
|
+
rechargePortalUrl: rechargeConfig.rechargePortalUrl || 'https://players.playkit.ai/recharge',
|
|
3882
3902
|
showDailyRefreshToast: (_d = rechargeConfig.showDailyRefreshToast) !== null && _d !== void 0 ? _d : true,
|
|
3883
3903
|
};
|
|
3884
3904
|
}
|
|
@@ -3893,9 +3913,7 @@
|
|
|
3893
3913
|
}
|
|
3894
3914
|
try {
|
|
3895
3915
|
// Build headers with X-Game-Id to support Global Developer Token
|
|
3896
|
-
const headers = {
|
|
3897
|
-
Authorization: `Bearer ${token}`,
|
|
3898
|
-
};
|
|
3916
|
+
const headers = Object.assign({ Authorization: `Bearer ${token}` }, getSDKHeaders());
|
|
3899
3917
|
if (this.gameId) {
|
|
3900
3918
|
headers['X-Game-Id'] = this.gameId;
|
|
3901
3919
|
}
|
|
@@ -3995,10 +4013,7 @@
|
|
|
3995
4013
|
try {
|
|
3996
4014
|
const response = await fetch(`${this.baseURL}${SET_NICKNAME_ENDPOINT}`, {
|
|
3997
4015
|
method: 'POST',
|
|
3998
|
-
headers: {
|
|
3999
|
-
Authorization: `Bearer ${token}`,
|
|
4000
|
-
'Content-Type': 'application/json',
|
|
4001
|
-
},
|
|
4016
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
4002
4017
|
body: JSON.stringify({ nickname: trimmed }),
|
|
4003
4018
|
});
|
|
4004
4019
|
if (!response.ok) {
|
|
@@ -4148,10 +4163,84 @@
|
|
|
4148
4163
|
}
|
|
4149
4164
|
}
|
|
4150
4165
|
|
|
4166
|
+
const VALID_PART_TYPES = new Set([
|
|
4167
|
+
'text',
|
|
4168
|
+
'image',
|
|
4169
|
+
'image_url',
|
|
4170
|
+
'file',
|
|
4171
|
+
'audio',
|
|
4172
|
+
'input_audio',
|
|
4173
|
+
]);
|
|
4174
|
+
function describePart(part) {
|
|
4175
|
+
if (part === null)
|
|
4176
|
+
return 'null';
|
|
4177
|
+
if (typeof part !== 'object')
|
|
4178
|
+
return typeof part;
|
|
4179
|
+
const keys = Object.keys(part).slice(0, 5).join(',');
|
|
4180
|
+
return `{${keys}}`;
|
|
4181
|
+
}
|
|
4182
|
+
/**
|
|
4183
|
+
* Validate that `messages` matches the SDK's `Message[]` runtime contract before
|
|
4184
|
+
* shipping to the chat API. Throws `PlayKitError('INVALID_MESSAGES')` when a
|
|
4185
|
+
* caller has wrapped a Message[] inside one user message's `content` (the
|
|
4186
|
+
* `[{role:'user', content: [{role,...}, ...]}]` anti-pattern that bypasses the
|
|
4187
|
+
* `MessageContentPart` type at runtime).
|
|
4188
|
+
*
|
|
4189
|
+
* Does NOT auto-flatten — silently guessing system/user roles would mask bugs.
|
|
4190
|
+
*/
|
|
4191
|
+
function assertValidMessages(messages) {
|
|
4192
|
+
if (!Array.isArray(messages)) {
|
|
4193
|
+
throw new PlayKitError('messages must be an array of Message', 'INVALID_MESSAGES');
|
|
4194
|
+
}
|
|
4195
|
+
for (let i = 0; i < messages.length; i++) {
|
|
4196
|
+
const msg = messages[i];
|
|
4197
|
+
if (!msg || typeof msg !== 'object') {
|
|
4198
|
+
throw new PlayKitError(`messages[${i}] must be an object with {role, content}`, 'INVALID_MESSAGES');
|
|
4199
|
+
}
|
|
4200
|
+
const content = msg.content;
|
|
4201
|
+
if (typeof content === 'string' || content == null)
|
|
4202
|
+
continue;
|
|
4203
|
+
if (!Array.isArray(content)) {
|
|
4204
|
+
throw new PlayKitError(`messages[${i}].content must be a string or an array of content parts (got ${typeof content})`, 'INVALID_MESSAGES');
|
|
4205
|
+
}
|
|
4206
|
+
for (let j = 0; j < content.length; j++) {
|
|
4207
|
+
const part = content[j];
|
|
4208
|
+
if (!part || typeof part !== 'object') {
|
|
4209
|
+
throw new PlayKitError(`messages[${i}].content[${j}] must be a content part object (got ${typeof part})`, 'INVALID_MESSAGES');
|
|
4210
|
+
}
|
|
4211
|
+
const hasType = typeof part.type === 'string' && VALID_PART_TYPES.has(part.type);
|
|
4212
|
+
if (!hasType) {
|
|
4213
|
+
if ('role' in part && 'content' in part) {
|
|
4214
|
+
throw new PlayKitError(`messages[${i}].content[${j}] is shaped like a Message (has role/content) ` +
|
|
4215
|
+
`but content parts must be {type:'text'|'image'|'image_url'|'file'|'audio'|'input_audio',...}. ` +
|
|
4216
|
+
`Did you mean to pass that array as messages directly? ` +
|
|
4217
|
+
`e.g. \`messages: theArray\` instead of \`messages: [{role:'user', content: theArray}]\`. ` +
|
|
4218
|
+
`Got part ${describePart(part)}`, 'INVALID_MESSAGES');
|
|
4219
|
+
}
|
|
4220
|
+
throw new PlayKitError(`messages[${i}].content[${j}] is missing a recognized 'type' field ` +
|
|
4221
|
+
`(expected one of text|image|image_url|file|audio|input_audio). Got part ${describePart(part)}`, 'INVALID_MESSAGES');
|
|
4222
|
+
}
|
|
4223
|
+
}
|
|
4224
|
+
}
|
|
4225
|
+
}
|
|
4226
|
+
|
|
4151
4227
|
/**
|
|
4152
4228
|
* Chat provider for HTTP communication with chat API
|
|
4153
4229
|
*/
|
|
4154
|
-
|
|
4230
|
+
/**
|
|
4231
|
+
* Helper to extract string from MessageContent
|
|
4232
|
+
*/
|
|
4233
|
+
function contentToString$1(content) {
|
|
4234
|
+
if (!content)
|
|
4235
|
+
return '';
|
|
4236
|
+
if (typeof content === 'string')
|
|
4237
|
+
return content;
|
|
4238
|
+
// For array of content parts, extract text parts
|
|
4239
|
+
const textParts = content.filter(part => part.type === 'text');
|
|
4240
|
+
return textParts.map(part => part.text).join('');
|
|
4241
|
+
}
|
|
4242
|
+
// @ts-ignore - replaced at build time
|
|
4243
|
+
const DEFAULT_BASE_URL$3 = "https://api.playkit.ai";
|
|
4155
4244
|
class ChatProvider {
|
|
4156
4245
|
constructor(authManager, config) {
|
|
4157
4246
|
this.authManager = authManager;
|
|
@@ -4169,6 +4258,7 @@
|
|
|
4169
4258
|
*/
|
|
4170
4259
|
async chatCompletion(chatConfig) {
|
|
4171
4260
|
var _a;
|
|
4261
|
+
assertValidMessages(chatConfig.messages);
|
|
4172
4262
|
// Ensure token is valid, auto-refresh if needed (browser mode only)
|
|
4173
4263
|
await this.authManager.ensureValidToken();
|
|
4174
4264
|
const token = this.authManager.getToken();
|
|
@@ -4190,10 +4280,7 @@
|
|
|
4190
4280
|
try {
|
|
4191
4281
|
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
4192
4282
|
method: 'POST',
|
|
4193
|
-
headers: {
|
|
4194
|
-
Authorization: `Bearer ${token}`,
|
|
4195
|
-
'Content-Type': 'application/json',
|
|
4196
|
-
},
|
|
4283
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
4197
4284
|
body: JSON.stringify(requestBody),
|
|
4198
4285
|
});
|
|
4199
4286
|
if (!response.ok) {
|
|
@@ -4228,6 +4315,7 @@
|
|
|
4228
4315
|
*/
|
|
4229
4316
|
async chatCompletionStream(chatConfig) {
|
|
4230
4317
|
var _a;
|
|
4318
|
+
assertValidMessages(chatConfig.messages);
|
|
4231
4319
|
// Ensure token is valid, auto-refresh if needed (browser mode only)
|
|
4232
4320
|
await this.authManager.ensureValidToken();
|
|
4233
4321
|
const token = this.authManager.getToken();
|
|
@@ -4249,10 +4337,7 @@
|
|
|
4249
4337
|
try {
|
|
4250
4338
|
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
4251
4339
|
method: 'POST',
|
|
4252
|
-
headers: {
|
|
4253
|
-
Authorization: `Bearer ${token}`,
|
|
4254
|
-
'Content-Type': 'application/json',
|
|
4255
|
-
},
|
|
4340
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
4256
4341
|
body: JSON.stringify(requestBody),
|
|
4257
4342
|
});
|
|
4258
4343
|
if (!response.ok) {
|
|
@@ -4289,6 +4374,7 @@
|
|
|
4289
4374
|
*/
|
|
4290
4375
|
async chatCompletionWithTools(chatConfig) {
|
|
4291
4376
|
var _a, _b;
|
|
4377
|
+
assertValidMessages(chatConfig.messages);
|
|
4292
4378
|
const token = this.authManager.getToken();
|
|
4293
4379
|
if (!token) {
|
|
4294
4380
|
throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
|
|
@@ -4315,10 +4401,7 @@
|
|
|
4315
4401
|
try {
|
|
4316
4402
|
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
4317
4403
|
method: 'POST',
|
|
4318
|
-
headers: {
|
|
4319
|
-
Authorization: `Bearer ${token}`,
|
|
4320
|
-
'Content-Type': 'application/json',
|
|
4321
|
-
},
|
|
4404
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
4322
4405
|
body: JSON.stringify(requestBody),
|
|
4323
4406
|
});
|
|
4324
4407
|
if (!response.ok) {
|
|
@@ -4349,6 +4432,7 @@
|
|
|
4349
4432
|
*/
|
|
4350
4433
|
async chatCompletionWithToolsStream(chatConfig) {
|
|
4351
4434
|
var _a, _b;
|
|
4435
|
+
assertValidMessages(chatConfig.messages);
|
|
4352
4436
|
const token = this.authManager.getToken();
|
|
4353
4437
|
if (!token) {
|
|
4354
4438
|
throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
|
|
@@ -4375,10 +4459,7 @@
|
|
|
4375
4459
|
try {
|
|
4376
4460
|
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
4377
4461
|
method: 'POST',
|
|
4378
|
-
headers: {
|
|
4379
|
-
Authorization: `Bearer ${token}`,
|
|
4380
|
-
'Content-Type': 'application/json',
|
|
4381
|
-
},
|
|
4462
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
4382
4463
|
body: JSON.stringify(requestBody),
|
|
4383
4464
|
});
|
|
4384
4465
|
if (!response.ok) {
|
|
@@ -4448,10 +4529,7 @@
|
|
|
4448
4529
|
try {
|
|
4449
4530
|
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
4450
4531
|
method: 'POST',
|
|
4451
|
-
headers: {
|
|
4452
|
-
Authorization: `Bearer ${token}`,
|
|
4453
|
-
'Content-Type': 'application/json',
|
|
4454
|
-
},
|
|
4532
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
4455
4533
|
body: JSON.stringify(requestBody),
|
|
4456
4534
|
});
|
|
4457
4535
|
if (!response.ok) {
|
|
@@ -4469,11 +4547,12 @@
|
|
|
4469
4547
|
this.playerClient.checkBalanceAfterApiCall().catch(() => { });
|
|
4470
4548
|
}
|
|
4471
4549
|
// Parse the response content as JSON
|
|
4472
|
-
const
|
|
4473
|
-
if (!
|
|
4550
|
+
const rawContent = (_a = result.choices[0]) === null || _a === void 0 ? void 0 : _a.message.content;
|
|
4551
|
+
if (!rawContent) {
|
|
4474
4552
|
throw new PlayKitError('No content in response', 'NO_CONTENT');
|
|
4475
4553
|
}
|
|
4476
4554
|
try {
|
|
4555
|
+
const content = contentToString$1(rawContent);
|
|
4477
4556
|
return JSON.parse(content);
|
|
4478
4557
|
}
|
|
4479
4558
|
catch (parseError) {
|
|
@@ -4492,7 +4571,8 @@
|
|
|
4492
4571
|
/**
|
|
4493
4572
|
* Image generation provider for HTTP communication with image API
|
|
4494
4573
|
*/
|
|
4495
|
-
|
|
4574
|
+
// @ts-ignore - replaced at build time
|
|
4575
|
+
const DEFAULT_BASE_URL$2 = "https://api.playkit.ai";
|
|
4496
4576
|
class ImageProvider {
|
|
4497
4577
|
constructor(authManager, config) {
|
|
4498
4578
|
this.authManager = authManager;
|
|
@@ -4545,10 +4625,7 @@
|
|
|
4545
4625
|
try {
|
|
4546
4626
|
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
4547
4627
|
method: 'POST',
|
|
4548
|
-
headers: {
|
|
4549
|
-
Authorization: `Bearer ${token}`,
|
|
4550
|
-
'Content-Type': 'application/json',
|
|
4551
|
-
},
|
|
4628
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
4552
4629
|
body: JSON.stringify(requestBody),
|
|
4553
4630
|
});
|
|
4554
4631
|
if (!response.ok) {
|
|
@@ -4583,7 +4660,8 @@
|
|
|
4583
4660
|
/**
|
|
4584
4661
|
* Transcription provider for HTTP communication with audio transcription API
|
|
4585
4662
|
*/
|
|
4586
|
-
|
|
4663
|
+
// @ts-ignore - replaced at build time
|
|
4664
|
+
const DEFAULT_BASE_URL$1 = "https://api.playkit.ai";
|
|
4587
4665
|
class TranscriptionProvider {
|
|
4588
4666
|
constructor(authManager, config) {
|
|
4589
4667
|
this.authManager = authManager;
|
|
@@ -4648,10 +4726,7 @@
|
|
|
4648
4726
|
try {
|
|
4649
4727
|
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
4650
4728
|
method: 'POST',
|
|
4651
|
-
headers: {
|
|
4652
|
-
Authorization: `Bearer ${token}`,
|
|
4653
|
-
'Content-Type': 'application/json',
|
|
4654
|
-
},
|
|
4729
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
4655
4730
|
body: JSON.stringify(requestBody),
|
|
4656
4731
|
});
|
|
4657
4732
|
if (!response.ok) {
|
|
@@ -4797,9 +4872,18 @@
|
|
|
4797
4872
|
if (text) {
|
|
4798
4873
|
yield yield __await(text);
|
|
4799
4874
|
}
|
|
4800
|
-
|
|
4875
|
+
// Stream termination events
|
|
4876
|
+
if (parsed.type === 'done' || parsed.type === 'finish' || parsed.finish_reason) {
|
|
4877
|
+
return yield __await(void 0);
|
|
4878
|
+
}
|
|
4879
|
+
if (parsed.type === 'abort') {
|
|
4880
|
+
// Server-side timeout or cancellation — treat as end of stream
|
|
4801
4881
|
return yield __await(void 0);
|
|
4802
4882
|
}
|
|
4883
|
+
if (parsed.type === 'error') {
|
|
4884
|
+
// Server-side error event — throw to trigger onError callback
|
|
4885
|
+
throw new Error(parsed.errorText || parsed.error || 'Stream error');
|
|
4886
|
+
}
|
|
4803
4887
|
}
|
|
4804
4888
|
catch (error) {
|
|
4805
4889
|
// If JSON parse fails, treat as plain text
|
|
@@ -4898,6 +4982,18 @@
|
|
|
4898
4982
|
/**
|
|
4899
4983
|
* Chat client for AI text generation
|
|
4900
4984
|
*/
|
|
4985
|
+
/**
|
|
4986
|
+
* Helper to extract string from MessageContent
|
|
4987
|
+
*/
|
|
4988
|
+
function contentToString(content) {
|
|
4989
|
+
if (!content)
|
|
4990
|
+
return '';
|
|
4991
|
+
if (typeof content === 'string')
|
|
4992
|
+
return content;
|
|
4993
|
+
// For array of content parts, extract text parts
|
|
4994
|
+
const textParts = content.filter(part => part.type === 'text');
|
|
4995
|
+
return textParts.map(part => part.text).join('');
|
|
4996
|
+
}
|
|
4901
4997
|
class ChatClient {
|
|
4902
4998
|
constructor(provider, model) {
|
|
4903
4999
|
this.schemaLibrary = null;
|
|
@@ -4947,7 +5043,7 @@
|
|
|
4947
5043
|
throw new Error('No choices in response');
|
|
4948
5044
|
}
|
|
4949
5045
|
return {
|
|
4950
|
-
content: choice.message.content,
|
|
5046
|
+
content: contentToString(choice.message.content),
|
|
4951
5047
|
model: response.model,
|
|
4952
5048
|
finishReason: choice.finish_reason,
|
|
4953
5049
|
usage: response.usage
|
|
@@ -5018,9 +5114,10 @@
|
|
|
5018
5114
|
}
|
|
5019
5115
|
// Extract user message content from the last user message
|
|
5020
5116
|
const lastUserMessage = [...messages].reverse().find(m => m.role === 'user');
|
|
5021
|
-
const prompt = (lastUserMessage === null || lastUserMessage === void 0 ? void 0 : lastUserMessage.content)
|
|
5117
|
+
const prompt = contentToString(lastUserMessage === null || lastUserMessage === void 0 ? void 0 : lastUserMessage.content);
|
|
5022
5118
|
// Build system message from messages array
|
|
5023
|
-
const
|
|
5119
|
+
const systemMessageContent = (_a = messages.find(m => m.role === 'system')) === null || _a === void 0 ? void 0 : _a.content;
|
|
5120
|
+
const systemMessage = contentToString(systemMessageContent) || undefined;
|
|
5024
5121
|
return this.generateStructuredWithSchema(schemaEntry.schema, prompt, Object.assign({ schemaName, schemaDescription: schemaEntry.description, systemMessage }, options));
|
|
5025
5122
|
}
|
|
5026
5123
|
/**
|
|
@@ -5111,7 +5208,7 @@
|
|
|
5111
5208
|
throw new Error('No choices in response');
|
|
5112
5209
|
}
|
|
5113
5210
|
return {
|
|
5114
|
-
content: choice.message.content
|
|
5211
|
+
content: contentToString(choice.message.content),
|
|
5115
5212
|
model: response.model,
|
|
5116
5213
|
finishReason: choice.finish_reason,
|
|
5117
5214
|
usage: response.usage
|
|
@@ -5175,7 +5272,7 @@
|
|
|
5175
5272
|
return new Promise((resolve, reject) => {
|
|
5176
5273
|
const img = new Image();
|
|
5177
5274
|
img.onload = () => resolve(img);
|
|
5178
|
-
img.onerror = (
|
|
5275
|
+
img.onerror = (_e) => reject(new Error('Failed to load image'));
|
|
5179
5276
|
img.src = this.toDataURL();
|
|
5180
5277
|
});
|
|
5181
5278
|
}
|
|
@@ -5189,13 +5286,14 @@
|
|
|
5189
5286
|
* Generate a single image
|
|
5190
5287
|
*/
|
|
5191
5288
|
async generateImage(config) {
|
|
5289
|
+
var _a;
|
|
5192
5290
|
const imageConfig = Object.assign(Object.assign({}, config), { model: config.model || this.model, n: 1 });
|
|
5193
5291
|
const response = await this.provider.generateImages(imageConfig);
|
|
5194
5292
|
const imageData = response.data[0];
|
|
5195
5293
|
if (!imageData || !imageData.b64_json) {
|
|
5196
5294
|
throw new Error('No image data in response');
|
|
5197
5295
|
}
|
|
5198
|
-
return new GeneratedImageImpl(imageData.b64_json, config.prompt, imageData.revised_prompt, config.size, imageData.b64_json_original, imageData.transparent_success);
|
|
5296
|
+
return new GeneratedImageImpl(imageData.b64_json, config.prompt, (_a = imageData.revised_prompt) !== null && _a !== void 0 ? _a : config.prompt, config.size, imageData.b64_json_original, imageData.transparent_success);
|
|
5199
5297
|
}
|
|
5200
5298
|
/**
|
|
5201
5299
|
* Generate multiple images
|
|
@@ -5204,10 +5302,11 @@
|
|
|
5204
5302
|
const imageConfig = Object.assign(Object.assign({}, config), { model: config.model || this.model, n: config.n || 1 });
|
|
5205
5303
|
const response = await this.provider.generateImages(imageConfig);
|
|
5206
5304
|
return response.data.map((imageData) => {
|
|
5305
|
+
var _a;
|
|
5207
5306
|
if (!imageData.b64_json) {
|
|
5208
5307
|
throw new Error('No image data in response');
|
|
5209
5308
|
}
|
|
5210
|
-
return new GeneratedImageImpl(imageData.b64_json, config.prompt, imageData.revised_prompt, config.size, imageData.b64_json_original, imageData.transparent_success);
|
|
5309
|
+
return new GeneratedImageImpl(imageData.b64_json, config.prompt, (_a = imageData.revised_prompt) !== null && _a !== void 0 ? _a : config.prompt, config.size, imageData.b64_json_original, imageData.transparent_success);
|
|
5211
5310
|
});
|
|
5212
5311
|
}
|
|
5213
5312
|
/**
|
|
@@ -5326,1019 +5425,1117 @@
|
|
|
5326
5425
|
}
|
|
5327
5426
|
|
|
5328
5427
|
/**
|
|
5329
|
-
*
|
|
5330
|
-
* Automatically handles conversation history
|
|
5428
|
+
* Global AI Context Manager for managing NPC conversations and player context.
|
|
5331
5429
|
*
|
|
5332
|
-
*
|
|
5333
|
-
* -
|
|
5334
|
-
* -
|
|
5335
|
-
* -
|
|
5336
|
-
* - Automatic conversation history management
|
|
5430
|
+
* Features:
|
|
5431
|
+
* - Player description management
|
|
5432
|
+
* - NPC conversation tracking
|
|
5433
|
+
* - Automatic conversation compaction (AutoCompact)
|
|
5337
5434
|
*/
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5435
|
+
/**
|
|
5436
|
+
* Global AI Context Manager
|
|
5437
|
+
* Manages NPC conversations and player context across the application
|
|
5438
|
+
*/
|
|
5439
|
+
class AIContextManager extends EventEmitter {
|
|
5440
|
+
constructor(config) {
|
|
5441
|
+
var _a, _b, _c, _d, _e;
|
|
5341
5442
|
super();
|
|
5342
|
-
this.
|
|
5343
|
-
this.
|
|
5344
|
-
this.
|
|
5345
|
-
|
|
5346
|
-
this.
|
|
5347
|
-
this.
|
|
5348
|
-
this.
|
|
5349
|
-
this.
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5443
|
+
this.playerDescription = null;
|
|
5444
|
+
this.playerPrompt = null;
|
|
5445
|
+
this.playerMemories = new Map();
|
|
5446
|
+
this.npcStates = new Map();
|
|
5447
|
+
this.autoCompactTimer = null;
|
|
5448
|
+
this.chatClientFactory = null;
|
|
5449
|
+
this.logger = Logger.getLogger('AIContextManager');
|
|
5450
|
+
this.config = {
|
|
5451
|
+
enableAutoCompact: (_a = config === null || config === void 0 ? void 0 : config.enableAutoCompact) !== null && _a !== void 0 ? _a : false,
|
|
5452
|
+
autoCompactMinMessages: (_b = config === null || config === void 0 ? void 0 : config.autoCompactMinMessages) !== null && _b !== void 0 ? _b : 20,
|
|
5453
|
+
autoCompactTimeoutSeconds: (_c = config === null || config === void 0 ? void 0 : config.autoCompactTimeoutSeconds) !== null && _c !== void 0 ? _c : 300,
|
|
5454
|
+
autoCompactCheckInterval: (_d = config === null || config === void 0 ? void 0 : config.autoCompactCheckInterval) !== null && _d !== void 0 ? _d : 60000,
|
|
5455
|
+
fastModel: (_e = config === null || config === void 0 ? void 0 : config.fastModel) !== null && _e !== void 0 ? _e : '',
|
|
5456
|
+
};
|
|
5457
|
+
// Start auto-compact check if enabled
|
|
5458
|
+
if (this.config.enableAutoCompact) {
|
|
5459
|
+
this.startAutoCompactCheck();
|
|
5460
|
+
}
|
|
5354
5461
|
}
|
|
5355
|
-
// =====
|
|
5462
|
+
// ===== Singleton Pattern =====
|
|
5356
5463
|
/**
|
|
5357
|
-
*
|
|
5464
|
+
* Get the singleton instance of AIContextManager
|
|
5465
|
+
* Creates a new instance if one doesn't exist
|
|
5358
5466
|
*/
|
|
5359
|
-
|
|
5360
|
-
|
|
5467
|
+
static getInstance(config) {
|
|
5468
|
+
if (!AIContextManager._instance) {
|
|
5469
|
+
AIContextManager._instance = new AIContextManager(config);
|
|
5470
|
+
}
|
|
5471
|
+
return AIContextManager._instance;
|
|
5361
5472
|
}
|
|
5362
|
-
// ===== Character Design & Memory System =====
|
|
5363
5473
|
/**
|
|
5364
|
-
*
|
|
5365
|
-
* The system prompt is composed of CharacterDesign + all Memories.
|
|
5474
|
+
* Reset the singleton instance (useful for testing)
|
|
5366
5475
|
*/
|
|
5367
|
-
|
|
5368
|
-
|
|
5476
|
+
static resetInstance() {
|
|
5477
|
+
if (AIContextManager._instance) {
|
|
5478
|
+
AIContextManager._instance.destroy();
|
|
5479
|
+
AIContextManager._instance = null;
|
|
5480
|
+
}
|
|
5369
5481
|
}
|
|
5482
|
+
// ===== Configuration =====
|
|
5370
5483
|
/**
|
|
5371
|
-
*
|
|
5484
|
+
* Set the chat client factory for creating chat clients for summarization
|
|
5485
|
+
* Required for compaction to work
|
|
5372
5486
|
*/
|
|
5373
|
-
|
|
5374
|
-
|
|
5487
|
+
setChatClientFactory(factory) {
|
|
5488
|
+
this.chatClientFactory = factory;
|
|
5375
5489
|
}
|
|
5376
5490
|
/**
|
|
5377
|
-
*
|
|
5378
|
-
* This method is kept for backwards compatibility.
|
|
5491
|
+
* Update configuration
|
|
5379
5492
|
*/
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
this.
|
|
5493
|
+
setConfig(config) {
|
|
5494
|
+
const wasAutoCompactEnabled = this.config.enableAutoCompact;
|
|
5495
|
+
this.config = Object.assign(Object.assign({}, this.config), config);
|
|
5496
|
+
// Handle auto-compact state change
|
|
5497
|
+
if (config.enableAutoCompact !== undefined) {
|
|
5498
|
+
if (config.enableAutoCompact && !wasAutoCompactEnabled) {
|
|
5499
|
+
this.startAutoCompactCheck();
|
|
5500
|
+
}
|
|
5501
|
+
else if (!config.enableAutoCompact && wasAutoCompactEnabled) {
|
|
5502
|
+
this.stopAutoCompactCheck();
|
|
5503
|
+
}
|
|
5504
|
+
}
|
|
5383
5505
|
}
|
|
5506
|
+
// ===== Player Description =====
|
|
5384
5507
|
/**
|
|
5385
|
-
*
|
|
5386
|
-
*
|
|
5508
|
+
* Set the player's description for AI context.
|
|
5509
|
+
* Used when generating reply predictions and for NPC context.
|
|
5510
|
+
* @param description Description of the player character
|
|
5387
5511
|
*/
|
|
5388
|
-
|
|
5389
|
-
|
|
5512
|
+
setPlayerDescription(description) {
|
|
5513
|
+
this.playerDescription = description;
|
|
5514
|
+
this.emit('playerDescriptionChanged', description);
|
|
5390
5515
|
}
|
|
5391
5516
|
/**
|
|
5392
|
-
*
|
|
5393
|
-
*
|
|
5517
|
+
* Get the current player description.
|
|
5518
|
+
* @returns The player description, or null if not set
|
|
5519
|
+
*/
|
|
5520
|
+
getPlayerDescription() {
|
|
5521
|
+
return this.playerDescription;
|
|
5522
|
+
}
|
|
5523
|
+
/**
|
|
5524
|
+
* Clear the player description.
|
|
5525
|
+
*/
|
|
5526
|
+
clearPlayerDescription() {
|
|
5527
|
+
this.playerDescription = null;
|
|
5528
|
+
this.emit('playerDescriptionChanged', null);
|
|
5529
|
+
}
|
|
5530
|
+
// ===== Player Prompt & Memory (for Reply Prediction) =====
|
|
5531
|
+
/**
|
|
5532
|
+
* Set the player's character prompt/persona.
|
|
5533
|
+
* This defines how the player character speaks and behaves.
|
|
5534
|
+
* Used when generating reply predictions to match the player's tone.
|
|
5535
|
+
* @param prompt The player character's persona/prompt
|
|
5536
|
+
*/
|
|
5537
|
+
setPlayerPrompt(prompt) {
|
|
5538
|
+
this.playerPrompt = prompt;
|
|
5539
|
+
}
|
|
5540
|
+
/**
|
|
5541
|
+
* Get the current player prompt.
|
|
5542
|
+
* @returns The player prompt, or null if not set
|
|
5543
|
+
*/
|
|
5544
|
+
getPlayerPrompt() {
|
|
5545
|
+
return this.playerPrompt;
|
|
5546
|
+
}
|
|
5547
|
+
/**
|
|
5548
|
+
* Set or update a memory for the player character.
|
|
5549
|
+
* Memories are appended to the player prompt to form the full player context.
|
|
5394
5550
|
* Set memoryContent to null or empty to remove the memory.
|
|
5395
5551
|
* @param memoryName The name/key of the memory
|
|
5396
5552
|
* @param memoryContent The content of the memory. Null or empty to remove.
|
|
5397
5553
|
*/
|
|
5398
|
-
|
|
5554
|
+
setPlayerMemory(memoryName, memoryContent) {
|
|
5399
5555
|
if (!memoryName) {
|
|
5400
5556
|
this.logger.warn('Memory name cannot be empty');
|
|
5401
5557
|
return;
|
|
5402
5558
|
}
|
|
5403
5559
|
if (!memoryContent) {
|
|
5404
5560
|
// Remove memory if content is null or empty
|
|
5405
|
-
|
|
5406
|
-
this.memories.delete(memoryName);
|
|
5407
|
-
this.emit('memory_removed', memoryName);
|
|
5408
|
-
}
|
|
5561
|
+
this.playerMemories.delete(memoryName);
|
|
5409
5562
|
}
|
|
5410
5563
|
else {
|
|
5411
5564
|
// Add or update memory
|
|
5412
|
-
this.
|
|
5413
|
-
this.emit('memory_set', memoryName, memoryContent);
|
|
5565
|
+
this.playerMemories.set(memoryName, memoryContent);
|
|
5414
5566
|
}
|
|
5415
5567
|
}
|
|
5416
5568
|
/**
|
|
5417
|
-
* Get a specific memory by name.
|
|
5569
|
+
* Get a specific player memory by name.
|
|
5418
5570
|
* @param memoryName The name of the memory to retrieve
|
|
5419
5571
|
* @returns The memory content, or undefined if not found
|
|
5420
5572
|
*/
|
|
5421
|
-
|
|
5422
|
-
return this.
|
|
5573
|
+
getPlayerMemory(memoryName) {
|
|
5574
|
+
return this.playerMemories.get(memoryName);
|
|
5423
5575
|
}
|
|
5424
5576
|
/**
|
|
5425
|
-
* Get all memory names currently stored.
|
|
5577
|
+
* Get all player memory names currently stored.
|
|
5426
5578
|
* @returns Array of memory names
|
|
5427
5579
|
*/
|
|
5428
|
-
|
|
5429
|
-
return Array.from(this.
|
|
5580
|
+
getPlayerMemoryNames() {
|
|
5581
|
+
return Array.from(this.playerMemories.keys());
|
|
5430
5582
|
}
|
|
5431
5583
|
/**
|
|
5432
|
-
* Clear all memories (but keep
|
|
5584
|
+
* Clear all player memories (but keep player prompt).
|
|
5433
5585
|
*/
|
|
5434
|
-
|
|
5435
|
-
this.
|
|
5436
|
-
this.emit('memories_cleared');
|
|
5586
|
+
clearPlayerMemories() {
|
|
5587
|
+
this.playerMemories.clear();
|
|
5437
5588
|
}
|
|
5438
5589
|
/**
|
|
5439
|
-
* Build the complete
|
|
5590
|
+
* Build the complete player context from PlayerPrompt + PlayerMemories.
|
|
5591
|
+
* Used by NPCClient for generating reply predictions.
|
|
5592
|
+
* @returns The combined player context string, or null if no context is set
|
|
5440
5593
|
*/
|
|
5441
|
-
|
|
5594
|
+
buildPlayerContext() {
|
|
5442
5595
|
const parts = [];
|
|
5443
|
-
if (this.
|
|
5444
|
-
parts.push(this.
|
|
5596
|
+
if (this.playerPrompt) {
|
|
5597
|
+
parts.push(this.playerPrompt);
|
|
5445
5598
|
}
|
|
5446
|
-
if (this.
|
|
5447
|
-
const memoryStrings = Array.from(this.
|
|
5599
|
+
if (this.playerMemories.size > 0) {
|
|
5600
|
+
const memoryStrings = Array.from(this.playerMemories.entries())
|
|
5448
5601
|
.map(([name, content]) => `[${name}]: ${content}`);
|
|
5449
|
-
parts.push('Memories:\n' + memoryStrings.join('\n'));
|
|
5602
|
+
parts.push('Player Memories:\n' + memoryStrings.join('\n'));
|
|
5603
|
+
}
|
|
5604
|
+
if (parts.length === 0) {
|
|
5605
|
+
return null;
|
|
5450
5606
|
}
|
|
5451
5607
|
return parts.join('\n\n');
|
|
5452
5608
|
}
|
|
5453
|
-
// =====
|
|
5609
|
+
// ===== NPC Tracking =====
|
|
5454
5610
|
/**
|
|
5455
|
-
*
|
|
5611
|
+
* Register an NPC for context management.
|
|
5612
|
+
* @param npc The NPC client to register
|
|
5456
5613
|
*/
|
|
5457
|
-
|
|
5458
|
-
|
|
5614
|
+
registerNpc(npc) {
|
|
5615
|
+
if (!npc)
|
|
5616
|
+
return;
|
|
5617
|
+
if (!this.npcStates.has(npc)) {
|
|
5618
|
+
this.npcStates.set(npc, {
|
|
5619
|
+
lastConversationTime: new Date(),
|
|
5620
|
+
isCompacted: false,
|
|
5621
|
+
compactionCount: 0,
|
|
5622
|
+
});
|
|
5623
|
+
}
|
|
5459
5624
|
}
|
|
5460
5625
|
/**
|
|
5461
|
-
*
|
|
5626
|
+
* Unregister an NPC (call when NPC is destroyed/removed).
|
|
5627
|
+
* @param npc The NPC client to unregister
|
|
5462
5628
|
*/
|
|
5463
|
-
|
|
5464
|
-
|
|
5629
|
+
unregisterNpc(npc) {
|
|
5630
|
+
if (!npc)
|
|
5631
|
+
return;
|
|
5632
|
+
this.npcStates.delete(npc);
|
|
5465
5633
|
}
|
|
5466
5634
|
/**
|
|
5467
|
-
*
|
|
5468
|
-
*
|
|
5469
|
-
* @param
|
|
5470
|
-
* @returns Array of predicted player replies, or empty array on failure
|
|
5635
|
+
* Record that a conversation occurred with an NPC.
|
|
5636
|
+
* Called after each Talk() exchange.
|
|
5637
|
+
* @param npc The NPC client that had a conversation
|
|
5471
5638
|
*/
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
if (this.
|
|
5476
|
-
this.
|
|
5477
|
-
return [];
|
|
5478
|
-
}
|
|
5479
|
-
try {
|
|
5480
|
-
// Get last NPC message
|
|
5481
|
-
const lastNpcMessage = (_a = [...this.history]
|
|
5482
|
-
.reverse()
|
|
5483
|
-
.find(m => m.role === 'assistant')) === null || _a === void 0 ? void 0 : _a.content;
|
|
5484
|
-
if (!lastNpcMessage) {
|
|
5485
|
-
this.logger.info('No NPC message found to generate predictions from');
|
|
5486
|
-
return [];
|
|
5487
|
-
}
|
|
5488
|
-
// Build recent history (last 6 non-system messages)
|
|
5489
|
-
const recentHistory = this.history
|
|
5490
|
-
.filter(m => m.role !== 'system')
|
|
5491
|
-
.slice(-6)
|
|
5492
|
-
.map(m => `${m.role}: ${m.content}`);
|
|
5493
|
-
// Build prompt for prediction generation
|
|
5494
|
-
const prompt = `Based on the conversation history below, generate exactly ${predictionNum} natural and contextually appropriate responses that the player might say next.
|
|
5495
|
-
|
|
5496
|
-
Context:
|
|
5497
|
-
- This is a conversation between a player and an NPC in a game
|
|
5498
|
-
- The NPC just said: "${lastNpcMessage}"
|
|
5499
|
-
|
|
5500
|
-
Conversation history:
|
|
5501
|
-
${recentHistory.join('\n')}
|
|
5502
|
-
|
|
5503
|
-
Requirements:
|
|
5504
|
-
1. Each response should be 1-2 sentences maximum
|
|
5505
|
-
2. Responses should be diverse in tone and intent
|
|
5506
|
-
3. Include a mix of questions, statements, and action-oriented responses
|
|
5507
|
-
4. Responses should feel natural for a player character
|
|
5508
|
-
|
|
5509
|
-
Output ONLY a JSON array of ${predictionNum} strings, nothing else:
|
|
5510
|
-
["response1", "response2", "response3", "response4"]`;
|
|
5511
|
-
const result = await this.chatClient.textGeneration({
|
|
5512
|
-
messages: [{ role: 'user', content: prompt }],
|
|
5513
|
-
temperature: 0.8,
|
|
5514
|
-
model: this.fastModel,
|
|
5515
|
-
});
|
|
5516
|
-
if (!result.content) {
|
|
5517
|
-
this.logger.warn('Failed to generate predictions: empty response');
|
|
5518
|
-
return [];
|
|
5519
|
-
}
|
|
5520
|
-
// Parse JSON response
|
|
5521
|
-
const predictions = this.parsePredictionsFromJson(result.content, predictionNum);
|
|
5522
|
-
if (predictions.length > 0) {
|
|
5523
|
-
this.emit('replyPredictions', predictions);
|
|
5524
|
-
}
|
|
5525
|
-
return predictions;
|
|
5526
|
-
}
|
|
5527
|
-
catch (error) {
|
|
5528
|
-
this.logger.error('Error generating predictions:', error);
|
|
5529
|
-
return [];
|
|
5639
|
+
recordConversation(npc) {
|
|
5640
|
+
if (!npc)
|
|
5641
|
+
return;
|
|
5642
|
+
if (!this.npcStates.has(npc)) {
|
|
5643
|
+
this.registerNpc(npc);
|
|
5530
5644
|
}
|
|
5645
|
+
const state = this.npcStates.get(npc);
|
|
5646
|
+
state.lastConversationTime = new Date();
|
|
5647
|
+
state.isCompacted = false; // Reset compaction flag on new conversation
|
|
5531
5648
|
}
|
|
5532
5649
|
/**
|
|
5533
|
-
*
|
|
5650
|
+
* Get all registered NPCs
|
|
5534
5651
|
*/
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
// Try to find JSON array in response
|
|
5538
|
-
const startIndex = response.indexOf('[');
|
|
5539
|
-
const endIndex = response.lastIndexOf(']');
|
|
5540
|
-
if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) {
|
|
5541
|
-
this.logger.warn('Could not find JSON array in prediction response');
|
|
5542
|
-
return this.extractPredictionsFromText(response, expectedCount);
|
|
5543
|
-
}
|
|
5544
|
-
const jsonArray = response.substring(startIndex, endIndex + 1);
|
|
5545
|
-
const parsed = JSON.parse(jsonArray);
|
|
5546
|
-
if (Array.isArray(parsed)) {
|
|
5547
|
-
return parsed
|
|
5548
|
-
.filter(item => typeof item === 'string' && item.trim())
|
|
5549
|
-
.slice(0, expectedCount);
|
|
5550
|
-
}
|
|
5551
|
-
return [];
|
|
5552
|
-
}
|
|
5553
|
-
catch (error) {
|
|
5554
|
-
this.logger.warn('Failed to parse predictions JSON:', error);
|
|
5555
|
-
return this.extractPredictionsFromText(response, expectedCount);
|
|
5556
|
-
}
|
|
5652
|
+
getRegisteredNpcs() {
|
|
5653
|
+
return Array.from(this.npcStates.keys());
|
|
5557
5654
|
}
|
|
5558
5655
|
/**
|
|
5559
|
-
*
|
|
5656
|
+
* Get the conversation state for an NPC
|
|
5560
5657
|
*/
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
const lines = response.split(/[\n\r]+/).filter(line => line.trim());
|
|
5564
|
-
for (const line of lines) {
|
|
5565
|
-
let cleaned = line.trim();
|
|
5566
|
-
// Skip empty lines and JSON brackets
|
|
5567
|
-
if (!cleaned || cleaned === '[' || cleaned === ']')
|
|
5568
|
-
continue;
|
|
5569
|
-
// Remove common prefixes like "1.", "- ", etc.
|
|
5570
|
-
if (/^\d+\./.test(cleaned)) {
|
|
5571
|
-
cleaned = cleaned.replace(/^\d+\.\s*/, '');
|
|
5572
|
-
}
|
|
5573
|
-
else if (cleaned.startsWith('- ')) {
|
|
5574
|
-
cleaned = cleaned.substring(2);
|
|
5575
|
-
}
|
|
5576
|
-
// Remove surrounding quotes
|
|
5577
|
-
if (cleaned.startsWith('"') && cleaned.endsWith('"')) {
|
|
5578
|
-
cleaned = cleaned.slice(1, -1);
|
|
5579
|
-
}
|
|
5580
|
-
// Remove trailing comma
|
|
5581
|
-
if (cleaned.endsWith(',')) {
|
|
5582
|
-
cleaned = cleaned.slice(0, -1).trim();
|
|
5583
|
-
}
|
|
5584
|
-
if (cleaned && predictions.length < expectedCount) {
|
|
5585
|
-
predictions.push(cleaned);
|
|
5586
|
-
}
|
|
5587
|
-
}
|
|
5588
|
-
return predictions;
|
|
5658
|
+
getNpcState(npc) {
|
|
5659
|
+
return this.npcStates.get(npc);
|
|
5589
5660
|
}
|
|
5661
|
+
// ===== Auto Compaction =====
|
|
5590
5662
|
/**
|
|
5591
|
-
*
|
|
5663
|
+
* Check if an NPC is eligible for compaction.
|
|
5664
|
+
* @param npc The NPC to check
|
|
5665
|
+
* @returns True if eligible for compaction
|
|
5592
5666
|
*/
|
|
5593
|
-
|
|
5594
|
-
if (!
|
|
5595
|
-
return;
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5667
|
+
isEligibleForCompaction(npc) {
|
|
5668
|
+
if (!npc)
|
|
5669
|
+
return false;
|
|
5670
|
+
const state = this.npcStates.get(npc);
|
|
5671
|
+
if (!state)
|
|
5672
|
+
return false;
|
|
5673
|
+
// Check if already compacted since last conversation
|
|
5674
|
+
if (state.isCompacted)
|
|
5675
|
+
return false;
|
|
5676
|
+
// Check message count
|
|
5677
|
+
const history = npc.getHistory();
|
|
5678
|
+
const nonSystemMessages = history.filter(m => m.role !== 'system').length;
|
|
5679
|
+
if (nonSystemMessages < this.config.autoCompactMinMessages)
|
|
5680
|
+
return false;
|
|
5681
|
+
// Check time since last conversation
|
|
5682
|
+
const timeSinceLastConversation = (Date.now() - state.lastConversationTime.getTime()) / 1000;
|
|
5683
|
+
if (timeSinceLastConversation < this.config.autoCompactTimeoutSeconds)
|
|
5684
|
+
return false;
|
|
5685
|
+
return true;
|
|
5600
5686
|
}
|
|
5601
|
-
// ===== Main API - Talk Methods =====
|
|
5602
5687
|
/**
|
|
5603
|
-
*
|
|
5688
|
+
* Manually trigger conversation compaction for a specific NPC.
|
|
5689
|
+
* Summarizes the conversation history and stores it as a memory.
|
|
5690
|
+
* @param npc The NPC to compact
|
|
5691
|
+
* @returns True if compaction succeeded
|
|
5604
5692
|
*/
|
|
5605
|
-
async
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
const userMessage = { role: 'user', content: message };
|
|
5610
|
-
this.history.push(userMessage);
|
|
5611
|
-
// Build messages array with system prompt
|
|
5612
|
-
const messages = [
|
|
5613
|
-
{ role: 'system', content: this.buildSystemPrompt() },
|
|
5614
|
-
...this.history,
|
|
5615
|
-
];
|
|
5616
|
-
// Generate response
|
|
5617
|
-
const result = await this.chatClient.textGeneration({
|
|
5618
|
-
messages,
|
|
5619
|
-
temperature: this.temperature,
|
|
5620
|
-
});
|
|
5621
|
-
// Add assistant response to history
|
|
5622
|
-
const assistantMessage = { role: 'assistant', content: result.content };
|
|
5623
|
-
this.history.push(assistantMessage);
|
|
5624
|
-
// Trim history if needed
|
|
5625
|
-
this.trimHistory();
|
|
5626
|
-
this.emit('response', result.content);
|
|
5627
|
-
// Trigger reply prediction generation (fire and forget)
|
|
5628
|
-
this.triggerReplyPrediction();
|
|
5629
|
-
return result.content;
|
|
5630
|
-
}
|
|
5631
|
-
finally {
|
|
5632
|
-
this._isTalking = false;
|
|
5693
|
+
async compactConversation(npc) {
|
|
5694
|
+
if (!npc) {
|
|
5695
|
+
this.logger.warn('Cannot compact: NPC is null');
|
|
5696
|
+
return false;
|
|
5633
5697
|
}
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
*/
|
|
5638
|
-
async talkStream(message, onChunk, onComplete) {
|
|
5639
|
-
this._isTalking = true;
|
|
5640
|
-
try {
|
|
5641
|
-
// Add user message to history
|
|
5642
|
-
const userMessage = { role: 'user', content: message };
|
|
5643
|
-
this.history.push(userMessage);
|
|
5644
|
-
// Build messages array with system prompt
|
|
5645
|
-
const messages = [
|
|
5646
|
-
{ role: 'system', content: this.buildSystemPrompt() },
|
|
5647
|
-
...this.history,
|
|
5648
|
-
];
|
|
5649
|
-
// Generate response
|
|
5650
|
-
await this.chatClient.textGenerationStream({
|
|
5651
|
-
messages,
|
|
5652
|
-
temperature: this.temperature,
|
|
5653
|
-
onChunk,
|
|
5654
|
-
onComplete: (fullText) => {
|
|
5655
|
-
this._isTalking = false;
|
|
5656
|
-
// Add assistant response to history
|
|
5657
|
-
const assistantMessage = { role: 'assistant', content: fullText };
|
|
5658
|
-
this.history.push(assistantMessage);
|
|
5659
|
-
// Trim history if needed
|
|
5660
|
-
this.trimHistory();
|
|
5661
|
-
this.emit('response', fullText);
|
|
5662
|
-
// Trigger reply prediction generation (fire and forget)
|
|
5663
|
-
this.triggerReplyPrediction();
|
|
5664
|
-
if (onComplete) {
|
|
5665
|
-
onComplete(fullText);
|
|
5666
|
-
}
|
|
5667
|
-
},
|
|
5668
|
-
});
|
|
5698
|
+
if (!this.chatClientFactory) {
|
|
5699
|
+
this.logger.error('Cannot compact: No chat client factory set. Call setChatClientFactory() first.');
|
|
5700
|
+
return false;
|
|
5669
5701
|
}
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5702
|
+
const history = npc.getHistory();
|
|
5703
|
+
const nonSystemMessages = history.filter(m => m.role !== 'system');
|
|
5704
|
+
if (nonSystemMessages.length < 2) {
|
|
5705
|
+
this.logger.info('Skipping compaction: not enough messages');
|
|
5706
|
+
return false;
|
|
5673
5707
|
}
|
|
5674
|
-
}
|
|
5675
|
-
/**
|
|
5676
|
-
* Talk with structured output
|
|
5677
|
-
* @deprecated Use talkWithActions instead for NPC decision-making with actions
|
|
5678
|
-
*/
|
|
5679
|
-
async talkStructured(message, schemaName) {
|
|
5680
|
-
this.logger.warn('talkStructured is deprecated. Use talkWithActions instead for NPC decision-making with actions.');
|
|
5681
|
-
// Add user message to history
|
|
5682
|
-
const userMessage = { role: 'user', content: message };
|
|
5683
|
-
this.history.push(userMessage);
|
|
5684
|
-
// Generate structured response
|
|
5685
|
-
const result = await this.chatClient.generateStructured({
|
|
5686
|
-
schemaName,
|
|
5687
|
-
prompt: message,
|
|
5688
|
-
messages: [{ role: 'system', content: this.buildSystemPrompt() }, ...this.history],
|
|
5689
|
-
temperature: this.temperature,
|
|
5690
|
-
});
|
|
5691
|
-
// Add a text representation to history
|
|
5692
|
-
const assistantMessage = {
|
|
5693
|
-
role: 'assistant',
|
|
5694
|
-
content: JSON.stringify(result),
|
|
5695
|
-
};
|
|
5696
|
-
this.history.push(assistantMessage);
|
|
5697
|
-
this.trimHistory();
|
|
5698
|
-
return result;
|
|
5699
|
-
}
|
|
5700
|
-
/**
|
|
5701
|
-
* Talk to the NPC with available actions (non-streaming)
|
|
5702
|
-
* @param message The message to send
|
|
5703
|
-
* @param actions List of actions the NPC can perform
|
|
5704
|
-
* @returns Response containing text and any action calls
|
|
5705
|
-
*/
|
|
5706
|
-
async talkWithActions(message, actions) {
|
|
5707
|
-
this._isTalking = true;
|
|
5708
5708
|
try {
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5709
|
+
this.logger.info(`Starting compaction (${nonSystemMessages.length} messages)`);
|
|
5710
|
+
// Build conversation text for summarization
|
|
5711
|
+
const conversationText = nonSystemMessages
|
|
5712
|
+
.map(m => `${m.role}: ${m.content}`)
|
|
5713
|
+
.join('\n');
|
|
5714
|
+
// Create summarization prompt
|
|
5715
|
+
const summaryPrompt = `Summarize the following conversation concisely. Focus on:
|
|
5716
|
+
1. Key topics discussed
|
|
5717
|
+
2. Important information exchanged
|
|
5718
|
+
3. Any decisions or commitments made
|
|
5719
|
+
4. The emotional tone
|
|
5720
|
+
|
|
5721
|
+
Keep the summary under 200 words. Write in third person.
|
|
5722
|
+
|
|
5723
|
+
Conversation:
|
|
5724
|
+
${conversationText}`;
|
|
5725
|
+
// Use chat client for summarization
|
|
5726
|
+
const chatClient = this.chatClientFactory();
|
|
5727
|
+
const result = await chatClient.textGeneration({
|
|
5728
|
+
messages: [{ role: 'user', content: summaryPrompt }],
|
|
5729
|
+
temperature: 0.5,
|
|
5730
|
+
model: this.config.fastModel || undefined,
|
|
5727
5731
|
});
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
};
|
|
5734
|
-
// Extract tool calls if any
|
|
5735
|
-
if (result.tool_calls) {
|
|
5736
|
-
response.actionCalls = result.tool_calls.map(tc => ({
|
|
5737
|
-
id: tc.id,
|
|
5738
|
-
actionName: tc.function.name,
|
|
5739
|
-
arguments: this.parseToolArguments(tc.function.arguments),
|
|
5740
|
-
}));
|
|
5741
|
-
response.hasActions = response.actionCalls.length > 0;
|
|
5742
|
-
}
|
|
5743
|
-
// Add assistant response to history
|
|
5744
|
-
const assistantMessage = {
|
|
5745
|
-
role: 'assistant',
|
|
5746
|
-
content: response.text,
|
|
5747
|
-
tool_calls: result.tool_calls,
|
|
5748
|
-
};
|
|
5749
|
-
this.history.push(assistantMessage);
|
|
5750
|
-
this.trimHistory();
|
|
5751
|
-
this.emit('response', response.text);
|
|
5752
|
-
if (response.hasActions) {
|
|
5753
|
-
this.emit('actions', response.actionCalls);
|
|
5732
|
+
if (!result.content) {
|
|
5733
|
+
const error = 'Empty response from summarization';
|
|
5734
|
+
this.logger.error(`Compaction failed: ${error}`);
|
|
5735
|
+
this.emit('compactionFailed', npc, error);
|
|
5736
|
+
return false;
|
|
5754
5737
|
}
|
|
5755
|
-
//
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
async talkWithActionsStream(message, actions, onChunk, onComplete) {
|
|
5768
|
-
this._isTalking = true;
|
|
5769
|
-
try {
|
|
5770
|
-
// Add user message to history
|
|
5771
|
-
const userMessage = { role: 'user', content: message };
|
|
5772
|
-
this.history.push(userMessage);
|
|
5773
|
-
// Convert NpcActions to ChatTools
|
|
5774
|
-
const tools = actions
|
|
5775
|
-
.filter(a => a && a.enabled !== false)
|
|
5776
|
-
.map(a => npcActionToTool(a));
|
|
5777
|
-
// Build messages array with system prompt
|
|
5778
|
-
const messages = [
|
|
5779
|
-
{ role: 'system', content: this.buildSystemPrompt() },
|
|
5780
|
-
...this.history,
|
|
5781
|
-
];
|
|
5782
|
-
// Generate response with tools (streaming)
|
|
5783
|
-
await this.chatClient.textGenerationWithToolsStream({
|
|
5784
|
-
messages,
|
|
5785
|
-
temperature: this.temperature,
|
|
5786
|
-
tools,
|
|
5787
|
-
tool_choice: 'auto',
|
|
5788
|
-
onChunk,
|
|
5789
|
-
onComplete: (result) => {
|
|
5790
|
-
this._isTalking = false;
|
|
5791
|
-
// Build response
|
|
5792
|
-
const response = {
|
|
5793
|
-
text: result.content || '',
|
|
5794
|
-
actionCalls: [],
|
|
5795
|
-
hasActions: false,
|
|
5796
|
-
};
|
|
5797
|
-
// Extract tool calls if any
|
|
5798
|
-
if (result.tool_calls) {
|
|
5799
|
-
response.actionCalls = result.tool_calls.map(tc => ({
|
|
5800
|
-
id: tc.id,
|
|
5801
|
-
actionName: tc.function.name,
|
|
5802
|
-
arguments: this.parseToolArguments(tc.function.arguments),
|
|
5803
|
-
}));
|
|
5804
|
-
response.hasActions = response.actionCalls.length > 0;
|
|
5805
|
-
}
|
|
5806
|
-
// Add assistant response to history
|
|
5807
|
-
const assistantMessage = {
|
|
5808
|
-
role: 'assistant',
|
|
5809
|
-
content: response.text,
|
|
5810
|
-
tool_calls: result.tool_calls,
|
|
5811
|
-
};
|
|
5812
|
-
this.history.push(assistantMessage);
|
|
5813
|
-
this.trimHistory();
|
|
5814
|
-
this.emit('response', response.text);
|
|
5815
|
-
if (response.hasActions) {
|
|
5816
|
-
this.emit('actions', response.actionCalls);
|
|
5817
|
-
}
|
|
5818
|
-
// Trigger reply prediction generation (fire and forget)
|
|
5819
|
-
this.triggerReplyPrediction();
|
|
5820
|
-
if (onComplete) {
|
|
5821
|
-
onComplete(response);
|
|
5822
|
-
}
|
|
5823
|
-
},
|
|
5824
|
-
});
|
|
5738
|
+
// Clear history and add summary as memory
|
|
5739
|
+
npc.clearHistory();
|
|
5740
|
+
npc.setMemory('PreviousConversationSummary', result.content);
|
|
5741
|
+
// Update state
|
|
5742
|
+
const state = this.npcStates.get(npc);
|
|
5743
|
+
if (state) {
|
|
5744
|
+
state.isCompacted = true;
|
|
5745
|
+
state.compactionCount++;
|
|
5746
|
+
}
|
|
5747
|
+
this.logger.info(`Compaction completed. Summary: ${result.content.substring(0, 100)}...`);
|
|
5748
|
+
this.emit('npcCompacted', npc);
|
|
5749
|
+
return true;
|
|
5825
5750
|
}
|
|
5826
5751
|
catch (error) {
|
|
5827
|
-
|
|
5828
|
-
|
|
5752
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5753
|
+
this.logger.error(`Compaction error: ${errorMessage}`);
|
|
5754
|
+
this.emit('compactionFailed', npc, errorMessage);
|
|
5755
|
+
return false;
|
|
5829
5756
|
}
|
|
5830
5757
|
}
|
|
5831
|
-
// ===== Action Results Reporting =====
|
|
5832
5758
|
/**
|
|
5833
|
-
*
|
|
5834
|
-
*
|
|
5759
|
+
* Compact all registered NPCs that meet the eligibility criteria.
|
|
5760
|
+
* @returns Number of NPCs successfully compacted
|
|
5835
5761
|
*/
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5762
|
+
async compactAllEligible() {
|
|
5763
|
+
const eligibleNpcs = Array.from(this.npcStates.keys()).filter(npc => this.isEligibleForCompaction(npc));
|
|
5764
|
+
if (eligibleNpcs.length === 0) {
|
|
5765
|
+
return 0;
|
|
5766
|
+
}
|
|
5767
|
+
this.logger.info(`Compacting ${eligibleNpcs.length} eligible NPCs`);
|
|
5768
|
+
let successCount = 0;
|
|
5769
|
+
for (const npc of eligibleNpcs) {
|
|
5770
|
+
const success = await this.compactConversation(npc);
|
|
5771
|
+
if (success)
|
|
5772
|
+
successCount++;
|
|
5843
5773
|
}
|
|
5774
|
+
return successCount;
|
|
5844
5775
|
}
|
|
5776
|
+
// ===== Auto Compact Timer =====
|
|
5845
5777
|
/**
|
|
5846
|
-
*
|
|
5778
|
+
* Start the auto-compact check timer
|
|
5847
5779
|
*/
|
|
5848
|
-
|
|
5849
|
-
this.
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5780
|
+
startAutoCompactCheck() {
|
|
5781
|
+
if (this.autoCompactTimer) {
|
|
5782
|
+
this.stopAutoCompactCheck();
|
|
5783
|
+
}
|
|
5784
|
+
this.autoCompactTimer = setInterval(() => {
|
|
5785
|
+
this.runAutoCompactCheck();
|
|
5786
|
+
}, this.config.autoCompactCheckInterval);
|
|
5854
5787
|
}
|
|
5855
5788
|
/**
|
|
5856
|
-
*
|
|
5789
|
+
* Stop the auto-compact check timer
|
|
5857
5790
|
*/
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5791
|
+
stopAutoCompactCheck() {
|
|
5792
|
+
if (this.autoCompactTimer) {
|
|
5793
|
+
clearInterval(this.autoCompactTimer);
|
|
5794
|
+
this.autoCompactTimer = null;
|
|
5861
5795
|
}
|
|
5862
|
-
|
|
5863
|
-
|
|
5796
|
+
}
|
|
5797
|
+
/**
|
|
5798
|
+
* Run a single auto-compact check
|
|
5799
|
+
*/
|
|
5800
|
+
async runAutoCompactCheck() {
|
|
5801
|
+
if (!this.config.enableAutoCompact)
|
|
5802
|
+
return;
|
|
5803
|
+
const eligibleNpcs = Array.from(this.npcStates.keys()).filter(npc => this.isEligibleForCompaction(npc));
|
|
5804
|
+
for (const npc of eligibleNpcs) {
|
|
5805
|
+
// Fire and forget - don't block
|
|
5806
|
+
this.compactConversation(npc).catch(err => {
|
|
5807
|
+
this.logger.error('Auto-compact error:', err);
|
|
5808
|
+
});
|
|
5864
5809
|
}
|
|
5865
5810
|
}
|
|
5866
|
-
// =====
|
|
5811
|
+
// ===== Lifecycle =====
|
|
5867
5812
|
/**
|
|
5868
|
-
*
|
|
5813
|
+
* Enable auto-compaction
|
|
5869
5814
|
*/
|
|
5870
|
-
|
|
5871
|
-
|
|
5815
|
+
enableAutoCompact() {
|
|
5816
|
+
this.config.enableAutoCompact = true;
|
|
5817
|
+
this.startAutoCompactCheck();
|
|
5872
5818
|
}
|
|
5873
5819
|
/**
|
|
5874
|
-
*
|
|
5820
|
+
* Disable auto-compaction
|
|
5875
5821
|
*/
|
|
5876
|
-
|
|
5877
|
-
|
|
5822
|
+
disableAutoCompact() {
|
|
5823
|
+
this.config.enableAutoCompact = false;
|
|
5824
|
+
this.stopAutoCompactCheck();
|
|
5878
5825
|
}
|
|
5879
5826
|
/**
|
|
5880
|
-
*
|
|
5881
|
-
* The character design and memories will be preserved.
|
|
5827
|
+
* Clean up resources
|
|
5882
5828
|
*/
|
|
5883
|
-
|
|
5829
|
+
destroy() {
|
|
5830
|
+
this.stopAutoCompactCheck();
|
|
5831
|
+
this.npcStates.clear();
|
|
5832
|
+
this.playerDescription = null;
|
|
5833
|
+
this.playerPrompt = null;
|
|
5834
|
+
this.playerMemories.clear();
|
|
5835
|
+
this.removeAllListeners();
|
|
5836
|
+
}
|
|
5837
|
+
}
|
|
5838
|
+
AIContextManager._instance = null;
|
|
5839
|
+
/**
|
|
5840
|
+
* Default AIContextManager instance
|
|
5841
|
+
* Can be used as a global context manager
|
|
5842
|
+
*/
|
|
5843
|
+
const defaultContextManager = AIContextManager.getInstance();
|
|
5844
|
+
|
|
5845
|
+
/**
|
|
5846
|
+
* NPC Client for simplified conversation management
|
|
5847
|
+
* Automatically handles conversation history
|
|
5848
|
+
*
|
|
5849
|
+
* Key Features:
|
|
5850
|
+
* - Call talk() for all interactions - actions are handled automatically
|
|
5851
|
+
* - Memory system for persistent NPC context
|
|
5852
|
+
* - Reply prediction for suggesting player responses
|
|
5853
|
+
* - Automatic conversation history management
|
|
5854
|
+
*/
|
|
5855
|
+
class NPCClient extends EventEmitter {
|
|
5856
|
+
constructor(chatClient, config) {
|
|
5857
|
+
var _a, _b, _c;
|
|
5858
|
+
super();
|
|
5859
|
+
this._isTalking = false;
|
|
5860
|
+
this.logger = Logger.getLogger('NPCClient');
|
|
5861
|
+
this.chatClient = chatClient;
|
|
5862
|
+
// Support both characterDesign and legacy systemPrompt
|
|
5863
|
+
this.characterDesign = (config === null || config === void 0 ? void 0 : config.characterDesign) || (config === null || config === void 0 ? void 0 : config.systemPrompt) || 'You are a helpful assistant.';
|
|
5864
|
+
this.temperature = (_a = config === null || config === void 0 ? void 0 : config.temperature) !== null && _a !== void 0 ? _a : 0.7;
|
|
5865
|
+
this.maxHistoryLength = (config === null || config === void 0 ? void 0 : config.maxHistoryLength) || 50;
|
|
5866
|
+
this.generateReplyPrediction = (_b = config === null || config === void 0 ? void 0 : config.generateReplyPrediction) !== null && _b !== void 0 ? _b : false;
|
|
5867
|
+
this.predictionCount = Math.max(2, Math.min(6, (_c = config === null || config === void 0 ? void 0 : config.predictionCount) !== null && _c !== void 0 ? _c : 4));
|
|
5868
|
+
this.fastModel = config === null || config === void 0 ? void 0 : config.fastModel;
|
|
5884
5869
|
this.history = [];
|
|
5885
|
-
this.
|
|
5870
|
+
this.memories = new Map();
|
|
5886
5871
|
}
|
|
5872
|
+
// ===== State Properties =====
|
|
5887
5873
|
/**
|
|
5888
|
-
*
|
|
5889
|
-
* @returns true if reverted, false if not enough history
|
|
5874
|
+
* Whether the NPC is currently processing a request
|
|
5890
5875
|
*/
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5876
|
+
get isTalking() {
|
|
5877
|
+
return this._isTalking;
|
|
5878
|
+
}
|
|
5879
|
+
// ===== Character Design & Memory System =====
|
|
5880
|
+
/**
|
|
5881
|
+
* Set the character design for the NPC.
|
|
5882
|
+
* The system prompt is composed of CharacterDesign + all Memories.
|
|
5883
|
+
*/
|
|
5884
|
+
setCharacterDesign(design) {
|
|
5885
|
+
this.characterDesign = design;
|
|
5886
|
+
}
|
|
5887
|
+
/**
|
|
5888
|
+
* Get the current character design
|
|
5889
|
+
*/
|
|
5890
|
+
getCharacterDesign() {
|
|
5891
|
+
return this.characterDesign;
|
|
5892
|
+
}
|
|
5893
|
+
/**
|
|
5894
|
+
* @deprecated Use setCharacterDesign instead.
|
|
5895
|
+
* This method is kept for backwards compatibility.
|
|
5896
|
+
*/
|
|
5897
|
+
setSystemPrompt(prompt) {
|
|
5898
|
+
this.logger.warn('setSystemPrompt is deprecated. Use setCharacterDesign instead.');
|
|
5899
|
+
this.setCharacterDesign(prompt);
|
|
5900
|
+
}
|
|
5901
|
+
/**
|
|
5902
|
+
* @deprecated Use getCharacterDesign instead.
|
|
5903
|
+
* This method is kept for backwards compatibility.
|
|
5904
|
+
*/
|
|
5905
|
+
getSystemPrompt() {
|
|
5906
|
+
return this.buildSystemPrompt();
|
|
5907
|
+
}
|
|
5908
|
+
/**
|
|
5909
|
+
* Set or update a memory for the NPC.
|
|
5910
|
+
* Memories are appended to the character design to form the system prompt.
|
|
5911
|
+
* Set memoryContent to null or empty to remove the memory.
|
|
5912
|
+
* @param memoryName The name/key of the memory
|
|
5913
|
+
* @param memoryContent The content of the memory. Null or empty to remove.
|
|
5914
|
+
*/
|
|
5915
|
+
setMemory(memoryName, memoryContent) {
|
|
5916
|
+
if (!memoryName) {
|
|
5917
|
+
this.logger.warn('Memory name cannot be empty');
|
|
5918
|
+
return;
|
|
5919
|
+
}
|
|
5920
|
+
if (!memoryContent) {
|
|
5921
|
+
// Remove memory if content is null or empty
|
|
5922
|
+
if (this.memories.has(memoryName)) {
|
|
5923
|
+
this.memories.delete(memoryName);
|
|
5924
|
+
this.emit('memory_removed', memoryName);
|
|
5901
5925
|
}
|
|
5902
5926
|
}
|
|
5903
|
-
|
|
5904
|
-
//
|
|
5905
|
-
this.
|
|
5906
|
-
this.
|
|
5907
|
-
this.emit('history_reverted');
|
|
5908
|
-
return true;
|
|
5927
|
+
else {
|
|
5928
|
+
// Add or update memory
|
|
5929
|
+
this.memories.set(memoryName, memoryContent);
|
|
5930
|
+
this.emit('memory_set', memoryName, memoryContent);
|
|
5909
5931
|
}
|
|
5910
|
-
return false;
|
|
5911
5932
|
}
|
|
5912
5933
|
/**
|
|
5913
|
-
*
|
|
5914
|
-
* @param
|
|
5915
|
-
* @returns
|
|
5934
|
+
* Get a specific memory by name.
|
|
5935
|
+
* @param memoryName The name of the memory to retrieve
|
|
5936
|
+
* @returns The memory content, or undefined if not found
|
|
5916
5937
|
*/
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
5938
|
+
getMemory(memoryName) {
|
|
5939
|
+
return this.memories.get(memoryName);
|
|
5940
|
+
}
|
|
5941
|
+
/**
|
|
5942
|
+
* Get all memory names currently stored.
|
|
5943
|
+
* @returns Array of memory names
|
|
5944
|
+
*/
|
|
5945
|
+
getMemoryNames() {
|
|
5946
|
+
return Array.from(this.memories.keys());
|
|
5947
|
+
}
|
|
5948
|
+
/**
|
|
5949
|
+
* Clear all memories (but keep character design).
|
|
5950
|
+
*/
|
|
5951
|
+
clearMemories() {
|
|
5952
|
+
this.memories.clear();
|
|
5953
|
+
this.emit('memories_cleared');
|
|
5954
|
+
}
|
|
5955
|
+
/**
|
|
5956
|
+
* Build the complete system prompt from CharacterDesign + Memories.
|
|
5957
|
+
*/
|
|
5958
|
+
buildSystemPrompt() {
|
|
5959
|
+
const parts = [];
|
|
5960
|
+
if (this.characterDesign) {
|
|
5961
|
+
parts.push(this.characterDesign);
|
|
5926
5962
|
}
|
|
5927
|
-
|
|
5963
|
+
if (this.memories.size > 0) {
|
|
5964
|
+
const memoryStrings = Array.from(this.memories.entries())
|
|
5965
|
+
.map(([name, content]) => `[${name}]: ${content}`);
|
|
5966
|
+
parts.push('Memories:\n' + memoryStrings.join('\n'));
|
|
5967
|
+
}
|
|
5968
|
+
return parts.join('\n\n');
|
|
5969
|
+
}
|
|
5970
|
+
// ===== Reply Prediction =====
|
|
5971
|
+
/**
|
|
5972
|
+
* Enable or disable automatic reply prediction
|
|
5973
|
+
*/
|
|
5974
|
+
setGenerateReplyPrediction(enabled) {
|
|
5975
|
+
this.generateReplyPrediction = enabled;
|
|
5928
5976
|
}
|
|
5929
5977
|
/**
|
|
5930
|
-
*
|
|
5931
|
-
* @deprecated Use revertHistory() or revertChatMessages() instead
|
|
5978
|
+
* Set the number of predictions to generate
|
|
5932
5979
|
*/
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
this.history = this.history.slice(0, index + 1);
|
|
5936
|
-
this.emit('history_reverted', index);
|
|
5937
|
-
}
|
|
5980
|
+
setPredictionCount(count) {
|
|
5981
|
+
this.predictionCount = Math.max(2, Math.min(6, count));
|
|
5938
5982
|
}
|
|
5939
5983
|
/**
|
|
5940
|
-
*
|
|
5984
|
+
* Manually generate reply predictions based on current conversation.
|
|
5985
|
+
* Uses the fast model for quick generation.
|
|
5986
|
+
* @param tempPrompt Optional temporary prompt to influence the prediction style/tone
|
|
5987
|
+
* @param count Number of predictions to generate (default: uses predictionCount property)
|
|
5988
|
+
* @returns Array of predicted player replies, or empty array on failure
|
|
5941
5989
|
*/
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
this.
|
|
5990
|
+
async generateReplyPredictions(tempPrompt, count) {
|
|
5991
|
+
var _a;
|
|
5992
|
+
const predictionNum = count !== null && count !== void 0 ? count : this.predictionCount;
|
|
5993
|
+
if (this.history.length < 2) {
|
|
5994
|
+
this.logger.info('Not enough conversation history to generate predictions');
|
|
5995
|
+
return [];
|
|
5996
|
+
}
|
|
5997
|
+
try {
|
|
5998
|
+
// Get last NPC message
|
|
5999
|
+
const lastNpcMessage = (_a = [...this.history]
|
|
6000
|
+
.reverse()
|
|
6001
|
+
.find(m => m.role === 'assistant')) === null || _a === void 0 ? void 0 : _a.content;
|
|
6002
|
+
if (!lastNpcMessage) {
|
|
6003
|
+
this.logger.info('No NPC message found to generate predictions from');
|
|
6004
|
+
return [];
|
|
6005
|
+
}
|
|
6006
|
+
// Build recent history (last 6 non-system messages)
|
|
6007
|
+
const recentHistory = this.history
|
|
6008
|
+
.filter(m => m.role !== 'system')
|
|
6009
|
+
.slice(-6)
|
|
6010
|
+
.map(m => `${m.role}: ${m.content}`);
|
|
6011
|
+
// Get player context from AIContextManager
|
|
6012
|
+
const contextManager = AIContextManager.getInstance();
|
|
6013
|
+
const playerContext = contextManager.buildPlayerContext();
|
|
6014
|
+
// Build player character section
|
|
6015
|
+
let playerCharacterSection = '';
|
|
6016
|
+
if (playerContext || tempPrompt) {
|
|
6017
|
+
playerCharacterSection = '\nPlayer Character:\n';
|
|
6018
|
+
if (playerContext) {
|
|
6019
|
+
playerCharacterSection += playerContext + '\n';
|
|
6020
|
+
}
|
|
6021
|
+
if (tempPrompt) {
|
|
6022
|
+
playerCharacterSection += `Additional guidance: ${tempPrompt}\n`;
|
|
6023
|
+
}
|
|
6024
|
+
}
|
|
6025
|
+
// Build prompt for prediction generation
|
|
6026
|
+
const prompt = `Based on the conversation history below, generate exactly ${predictionNum} natural and contextually appropriate responses that the player might say next.
|
|
6027
|
+
|
|
6028
|
+
Context:
|
|
6029
|
+
- This is a conversation between a player and an NPC in a game
|
|
6030
|
+
- The NPC just said: "${lastNpcMessage}"
|
|
6031
|
+
${playerCharacterSection}
|
|
6032
|
+
Conversation history:
|
|
6033
|
+
${recentHistory.join('\n')}
|
|
6034
|
+
|
|
6035
|
+
Requirements:
|
|
6036
|
+
1. Each response should be 1-2 sentences maximum
|
|
6037
|
+
2. Responses should be diverse in tone and intent
|
|
6038
|
+
3. Include a mix of questions, statements, and action-oriented responses
|
|
6039
|
+
4. Responses should feel natural for the player character${playerContext || tempPrompt ? ' and match their personality/tone' : ''}
|
|
6040
|
+
|
|
6041
|
+
Output ONLY a JSON array of ${predictionNum} strings, nothing else:
|
|
6042
|
+
["response1", "response2", "response3", "response4"]`;
|
|
6043
|
+
const result = await this.chatClient.textGeneration({
|
|
6044
|
+
messages: [{ role: 'user', content: prompt }],
|
|
6045
|
+
temperature: 0.8,
|
|
6046
|
+
model: this.fastModel,
|
|
6047
|
+
});
|
|
6048
|
+
if (!result.content) {
|
|
6049
|
+
this.logger.warn('Failed to generate predictions: empty response');
|
|
6050
|
+
return [];
|
|
6051
|
+
}
|
|
6052
|
+
// Parse JSON response
|
|
6053
|
+
const predictions = this.parsePredictionsFromJson(result.content, predictionNum);
|
|
6054
|
+
if (predictions.length > 0) {
|
|
6055
|
+
this.emit('replyPredictions', predictions);
|
|
6056
|
+
}
|
|
6057
|
+
return predictions;
|
|
6058
|
+
}
|
|
6059
|
+
catch (error) {
|
|
6060
|
+
this.logger.error('Error generating predictions:', error);
|
|
6061
|
+
return [];
|
|
6062
|
+
}
|
|
5945
6063
|
}
|
|
5946
6064
|
/**
|
|
5947
|
-
*
|
|
6065
|
+
* Parse predictions from JSON array response
|
|
5948
6066
|
*/
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
6067
|
+
parsePredictionsFromJson(response, expectedCount) {
|
|
6068
|
+
try {
|
|
6069
|
+
// Try to find JSON array in response
|
|
6070
|
+
const startIndex = response.indexOf('[');
|
|
6071
|
+
const endIndex = response.lastIndexOf(']');
|
|
6072
|
+
if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) {
|
|
6073
|
+
this.logger.warn('Could not find JSON array in prediction response');
|
|
6074
|
+
return this.extractPredictionsFromText(response, expectedCount);
|
|
6075
|
+
}
|
|
6076
|
+
const jsonArray = response.substring(startIndex, endIndex + 1);
|
|
6077
|
+
const parsed = JSON.parse(jsonArray);
|
|
6078
|
+
if (Array.isArray(parsed)) {
|
|
6079
|
+
return parsed
|
|
6080
|
+
.filter(item => typeof item === 'string' && item.trim())
|
|
6081
|
+
.slice(0, expectedCount);
|
|
6082
|
+
}
|
|
6083
|
+
return [];
|
|
6084
|
+
}
|
|
6085
|
+
catch (error) {
|
|
6086
|
+
this.logger.warn('Failed to parse predictions JSON:', error);
|
|
6087
|
+
return this.extractPredictionsFromText(response, expectedCount);
|
|
5953
6088
|
}
|
|
5954
|
-
this.appendMessage({ role: role, content });
|
|
5955
6089
|
}
|
|
5956
6090
|
/**
|
|
5957
|
-
*
|
|
6091
|
+
* Fallback: Extract predictions from text when JSON parsing fails
|
|
5958
6092
|
*/
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
6093
|
+
extractPredictionsFromText(response, expectedCount) {
|
|
6094
|
+
const predictions = [];
|
|
6095
|
+
const lines = response.split(/[\n\r]+/).filter(line => line.trim());
|
|
6096
|
+
for (const line of lines) {
|
|
6097
|
+
let cleaned = line.trim();
|
|
6098
|
+
// Skip empty lines and JSON brackets
|
|
6099
|
+
if (!cleaned || cleaned === '[' || cleaned === ']')
|
|
6100
|
+
continue;
|
|
6101
|
+
// Remove common prefixes like "1.", "- ", etc.
|
|
6102
|
+
if (/^\d+\./.test(cleaned)) {
|
|
6103
|
+
cleaned = cleaned.replace(/^\d+\.\s*/, '');
|
|
6104
|
+
}
|
|
6105
|
+
else if (cleaned.startsWith('- ')) {
|
|
6106
|
+
cleaned = cleaned.substring(2);
|
|
6107
|
+
}
|
|
6108
|
+
// Remove surrounding quotes
|
|
6109
|
+
if (cleaned.startsWith('"') && cleaned.endsWith('"')) {
|
|
6110
|
+
cleaned = cleaned.slice(1, -1);
|
|
6111
|
+
}
|
|
6112
|
+
// Remove trailing comma
|
|
6113
|
+
if (cleaned.endsWith(',')) {
|
|
6114
|
+
cleaned = cleaned.slice(0, -1).trim();
|
|
6115
|
+
}
|
|
6116
|
+
if (cleaned && predictions.length < expectedCount) {
|
|
6117
|
+
predictions.push(cleaned);
|
|
6118
|
+
}
|
|
5963
6119
|
}
|
|
6120
|
+
return predictions;
|
|
5964
6121
|
}
|
|
5965
|
-
// ===== Save/Load =====
|
|
5966
6122
|
/**
|
|
5967
|
-
*
|
|
5968
|
-
* Includes characterDesign, memories, and history.
|
|
6123
|
+
* Internal method to trigger prediction generation after NPC response
|
|
5969
6124
|
*/
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
6125
|
+
async triggerReplyPrediction() {
|
|
6126
|
+
if (!this.generateReplyPrediction)
|
|
6127
|
+
return;
|
|
6128
|
+
// Fire and forget - don't block the main response
|
|
6129
|
+
this.generateReplyPredictions().catch(err => {
|
|
6130
|
+
this.logger.error('Background prediction generation failed:', err);
|
|
6131
|
+
});
|
|
5977
6132
|
}
|
|
6133
|
+
// ===== Main API - Talk Methods =====
|
|
5978
6134
|
/**
|
|
5979
|
-
*
|
|
5980
|
-
* Restores characterDesign, memories, and history.
|
|
6135
|
+
* Talk to the NPC (non-streaming)
|
|
5981
6136
|
*/
|
|
5982
|
-
|
|
6137
|
+
async talk(message) {
|
|
6138
|
+
this._isTalking = true;
|
|
5983
6139
|
try {
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
this.
|
|
5987
|
-
//
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
this.
|
|
6003
|
-
|
|
6140
|
+
// Add user message to history
|
|
6141
|
+
const userMessage = { role: 'user', content: message };
|
|
6142
|
+
this.history.push(userMessage);
|
|
6143
|
+
// Build messages array with system prompt
|
|
6144
|
+
const messages = [
|
|
6145
|
+
{ role: 'system', content: this.buildSystemPrompt() },
|
|
6146
|
+
...this.history,
|
|
6147
|
+
];
|
|
6148
|
+
// Generate response
|
|
6149
|
+
const result = await this.chatClient.textGeneration({
|
|
6150
|
+
messages,
|
|
6151
|
+
temperature: this.temperature,
|
|
6152
|
+
});
|
|
6153
|
+
// Add assistant response to history
|
|
6154
|
+
const assistantMessage = { role: 'assistant', content: result.content };
|
|
6155
|
+
this.history.push(assistantMessage);
|
|
6156
|
+
// Trim history if needed
|
|
6157
|
+
this.trimHistory();
|
|
6158
|
+
this.emit('response', result.content);
|
|
6159
|
+
// Trigger reply prediction generation (fire and forget)
|
|
6160
|
+
this.triggerReplyPrediction();
|
|
6161
|
+
return result.content;
|
|
6004
6162
|
}
|
|
6005
|
-
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
/**
|
|
6009
|
-
* Global AI Context Manager for managing NPC conversations and player context.
|
|
6010
|
-
*
|
|
6011
|
-
* Features:
|
|
6012
|
-
* - Player description management
|
|
6013
|
-
* - NPC conversation tracking
|
|
6014
|
-
* - Automatic conversation compaction (AutoCompact)
|
|
6015
|
-
*/
|
|
6016
|
-
/**
|
|
6017
|
-
* Global AI Context Manager
|
|
6018
|
-
* Manages NPC conversations and player context across the application
|
|
6019
|
-
*/
|
|
6020
|
-
class AIContextManager extends EventEmitter {
|
|
6021
|
-
constructor(config) {
|
|
6022
|
-
var _a, _b, _c, _d, _e;
|
|
6023
|
-
super();
|
|
6024
|
-
this.playerDescription = null;
|
|
6025
|
-
this.npcStates = new Map();
|
|
6026
|
-
this.autoCompactTimer = null;
|
|
6027
|
-
this.chatClientFactory = null;
|
|
6028
|
-
this.logger = Logger.getLogger('AIContextManager');
|
|
6029
|
-
this.config = {
|
|
6030
|
-
enableAutoCompact: (_a = config === null || config === void 0 ? void 0 : config.enableAutoCompact) !== null && _a !== void 0 ? _a : false,
|
|
6031
|
-
autoCompactMinMessages: (_b = config === null || config === void 0 ? void 0 : config.autoCompactMinMessages) !== null && _b !== void 0 ? _b : 20,
|
|
6032
|
-
autoCompactTimeoutSeconds: (_c = config === null || config === void 0 ? void 0 : config.autoCompactTimeoutSeconds) !== null && _c !== void 0 ? _c : 300,
|
|
6033
|
-
autoCompactCheckInterval: (_d = config === null || config === void 0 ? void 0 : config.autoCompactCheckInterval) !== null && _d !== void 0 ? _d : 60000,
|
|
6034
|
-
fastModel: (_e = config === null || config === void 0 ? void 0 : config.fastModel) !== null && _e !== void 0 ? _e : '',
|
|
6035
|
-
};
|
|
6036
|
-
// Start auto-compact check if enabled
|
|
6037
|
-
if (this.config.enableAutoCompact) {
|
|
6038
|
-
this.startAutoCompactCheck();
|
|
6163
|
+
finally {
|
|
6164
|
+
this._isTalking = false;
|
|
6039
6165
|
}
|
|
6040
6166
|
}
|
|
6041
|
-
// ===== Singleton Pattern =====
|
|
6042
6167
|
/**
|
|
6043
|
-
*
|
|
6044
|
-
* Creates a new instance if one doesn't exist
|
|
6168
|
+
* Talk to the NPC with streaming
|
|
6045
6169
|
*/
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6170
|
+
async talkStream(message, onChunk, onComplete) {
|
|
6171
|
+
this._isTalking = true;
|
|
6172
|
+
try {
|
|
6173
|
+
// Add user message to history
|
|
6174
|
+
const userMessage = { role: 'user', content: message };
|
|
6175
|
+
this.history.push(userMessage);
|
|
6176
|
+
// Build messages array with system prompt
|
|
6177
|
+
const messages = [
|
|
6178
|
+
{ role: 'system', content: this.buildSystemPrompt() },
|
|
6179
|
+
...this.history,
|
|
6180
|
+
];
|
|
6181
|
+
// Generate response
|
|
6182
|
+
await this.chatClient.textGenerationStream({
|
|
6183
|
+
messages,
|
|
6184
|
+
temperature: this.temperature,
|
|
6185
|
+
onChunk,
|
|
6186
|
+
onComplete: (fullText) => {
|
|
6187
|
+
this._isTalking = false;
|
|
6188
|
+
// Add assistant response to history
|
|
6189
|
+
const assistantMessage = { role: 'assistant', content: fullText };
|
|
6190
|
+
this.history.push(assistantMessage);
|
|
6191
|
+
// Trim history if needed
|
|
6192
|
+
this.trimHistory();
|
|
6193
|
+
this.emit('response', fullText);
|
|
6194
|
+
// Trigger reply prediction generation (fire and forget)
|
|
6195
|
+
this.triggerReplyPrediction();
|
|
6196
|
+
if (onComplete) {
|
|
6197
|
+
onComplete(fullText);
|
|
6198
|
+
}
|
|
6199
|
+
},
|
|
6200
|
+
});
|
|
6049
6201
|
}
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
* Reset the singleton instance (useful for testing)
|
|
6054
|
-
*/
|
|
6055
|
-
static resetInstance() {
|
|
6056
|
-
if (AIContextManager._instance) {
|
|
6057
|
-
AIContextManager._instance.destroy();
|
|
6058
|
-
AIContextManager._instance = null;
|
|
6202
|
+
catch (error) {
|
|
6203
|
+
this._isTalking = false;
|
|
6204
|
+
throw error;
|
|
6059
6205
|
}
|
|
6060
6206
|
}
|
|
6061
|
-
// ===== Configuration =====
|
|
6062
6207
|
/**
|
|
6063
|
-
*
|
|
6064
|
-
*
|
|
6208
|
+
* Talk with structured output
|
|
6209
|
+
* @deprecated Use talkWithActions instead for NPC decision-making with actions
|
|
6065
6210
|
*/
|
|
6066
|
-
|
|
6067
|
-
this.
|
|
6211
|
+
async talkStructured(message, schemaName) {
|
|
6212
|
+
this.logger.warn('talkStructured is deprecated. Use talkWithActions instead for NPC decision-making with actions.');
|
|
6213
|
+
// Add user message to history
|
|
6214
|
+
const userMessage = { role: 'user', content: message };
|
|
6215
|
+
this.history.push(userMessage);
|
|
6216
|
+
// Generate structured response
|
|
6217
|
+
const result = await this.chatClient.generateStructured({
|
|
6218
|
+
schemaName,
|
|
6219
|
+
prompt: message,
|
|
6220
|
+
messages: [{ role: 'system', content: this.buildSystemPrompt() }, ...this.history],
|
|
6221
|
+
temperature: this.temperature,
|
|
6222
|
+
});
|
|
6223
|
+
// Add a text representation to history
|
|
6224
|
+
const assistantMessage = {
|
|
6225
|
+
role: 'assistant',
|
|
6226
|
+
content: JSON.stringify(result),
|
|
6227
|
+
};
|
|
6228
|
+
this.history.push(assistantMessage);
|
|
6229
|
+
this.trimHistory();
|
|
6230
|
+
return result;
|
|
6068
6231
|
}
|
|
6069
6232
|
/**
|
|
6070
|
-
*
|
|
6233
|
+
* Talk to the NPC with available actions (non-streaming)
|
|
6234
|
+
* @param message The message to send
|
|
6235
|
+
* @param actions List of actions the NPC can perform
|
|
6236
|
+
* @returns Response containing text and any action calls
|
|
6071
6237
|
*/
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
6238
|
+
async talkWithActions(message, actions) {
|
|
6239
|
+
this._isTalking = true;
|
|
6240
|
+
try {
|
|
6241
|
+
// Add user message to history
|
|
6242
|
+
const userMessage = { role: 'user', content: message };
|
|
6243
|
+
this.history.push(userMessage);
|
|
6244
|
+
// Convert NpcActions to ChatTools
|
|
6245
|
+
const tools = actions
|
|
6246
|
+
.filter(a => a && a.enabled !== false)
|
|
6247
|
+
.map(a => npcActionToTool(a));
|
|
6248
|
+
// Build messages array with system prompt
|
|
6249
|
+
const messages = [
|
|
6250
|
+
{ role: 'system', content: this.buildSystemPrompt() },
|
|
6251
|
+
...this.history,
|
|
6252
|
+
];
|
|
6253
|
+
// Generate response with tools
|
|
6254
|
+
const result = await this.chatClient.textGenerationWithTools({
|
|
6255
|
+
messages,
|
|
6256
|
+
temperature: this.temperature,
|
|
6257
|
+
tools,
|
|
6258
|
+
tool_choice: 'auto',
|
|
6259
|
+
});
|
|
6260
|
+
// Build response
|
|
6261
|
+
const response = {
|
|
6262
|
+
text: result.content || '',
|
|
6263
|
+
actionCalls: [],
|
|
6264
|
+
hasActions: false,
|
|
6265
|
+
};
|
|
6266
|
+
// Extract tool calls if any
|
|
6267
|
+
if (result.tool_calls) {
|
|
6268
|
+
response.actionCalls = result.tool_calls.map(tc => ({
|
|
6269
|
+
id: tc.id,
|
|
6270
|
+
actionName: tc.function.name,
|
|
6271
|
+
arguments: this.parseToolArguments(tc.function.arguments),
|
|
6272
|
+
}));
|
|
6273
|
+
response.hasActions = response.actionCalls.length > 0;
|
|
6079
6274
|
}
|
|
6080
|
-
|
|
6081
|
-
|
|
6275
|
+
// Add assistant response to history
|
|
6276
|
+
const assistantMessage = {
|
|
6277
|
+
role: 'assistant',
|
|
6278
|
+
content: response.text,
|
|
6279
|
+
tool_calls: result.tool_calls,
|
|
6280
|
+
};
|
|
6281
|
+
this.history.push(assistantMessage);
|
|
6282
|
+
this.trimHistory();
|
|
6283
|
+
this.emit('response', response.text);
|
|
6284
|
+
if (response.hasActions) {
|
|
6285
|
+
this.emit('actions', response.actionCalls);
|
|
6082
6286
|
}
|
|
6287
|
+
// Trigger reply prediction generation (fire and forget)
|
|
6288
|
+
this.triggerReplyPrediction();
|
|
6289
|
+
return response;
|
|
6290
|
+
}
|
|
6291
|
+
finally {
|
|
6292
|
+
this._isTalking = false;
|
|
6083
6293
|
}
|
|
6084
|
-
}
|
|
6085
|
-
// ===== Player Description =====
|
|
6086
|
-
/**
|
|
6087
|
-
* Set the player's description for AI context.
|
|
6088
|
-
* Used when generating reply predictions and for NPC context.
|
|
6089
|
-
* @param description Description of the player character
|
|
6090
|
-
*/
|
|
6091
|
-
setPlayerDescription(description) {
|
|
6092
|
-
this.playerDescription = description;
|
|
6093
|
-
this.emit('playerDescriptionChanged', description);
|
|
6094
|
-
}
|
|
6095
|
-
/**
|
|
6096
|
-
* Get the current player description.
|
|
6097
|
-
* @returns The player description, or null if not set
|
|
6098
|
-
*/
|
|
6099
|
-
getPlayerDescription() {
|
|
6100
|
-
return this.playerDescription;
|
|
6101
6294
|
}
|
|
6102
6295
|
/**
|
|
6103
|
-
*
|
|
6296
|
+
* Talk to the NPC with actions (streaming)
|
|
6297
|
+
* Text streams first, action calls are returned in onComplete
|
|
6104
6298
|
*/
|
|
6105
|
-
|
|
6106
|
-
this.
|
|
6107
|
-
|
|
6299
|
+
async talkWithActionsStream(message, actions, onChunk, onComplete) {
|
|
6300
|
+
this._isTalking = true;
|
|
6301
|
+
try {
|
|
6302
|
+
// Add user message to history
|
|
6303
|
+
const userMessage = { role: 'user', content: message };
|
|
6304
|
+
this.history.push(userMessage);
|
|
6305
|
+
// Convert NpcActions to ChatTools
|
|
6306
|
+
const tools = actions
|
|
6307
|
+
.filter(a => a && a.enabled !== false)
|
|
6308
|
+
.map(a => npcActionToTool(a));
|
|
6309
|
+
// Build messages array with system prompt
|
|
6310
|
+
const messages = [
|
|
6311
|
+
{ role: 'system', content: this.buildSystemPrompt() },
|
|
6312
|
+
...this.history,
|
|
6313
|
+
];
|
|
6314
|
+
// Generate response with tools (streaming)
|
|
6315
|
+
await this.chatClient.textGenerationWithToolsStream({
|
|
6316
|
+
messages,
|
|
6317
|
+
temperature: this.temperature,
|
|
6318
|
+
tools,
|
|
6319
|
+
tool_choice: 'auto',
|
|
6320
|
+
onChunk,
|
|
6321
|
+
onComplete: (result) => {
|
|
6322
|
+
this._isTalking = false;
|
|
6323
|
+
// Build response
|
|
6324
|
+
const response = {
|
|
6325
|
+
text: result.content || '',
|
|
6326
|
+
actionCalls: [],
|
|
6327
|
+
hasActions: false,
|
|
6328
|
+
};
|
|
6329
|
+
// Extract tool calls if any
|
|
6330
|
+
if (result.tool_calls) {
|
|
6331
|
+
response.actionCalls = result.tool_calls.map(tc => ({
|
|
6332
|
+
id: tc.id,
|
|
6333
|
+
actionName: tc.function.name,
|
|
6334
|
+
arguments: this.parseToolArguments(tc.function.arguments),
|
|
6335
|
+
}));
|
|
6336
|
+
response.hasActions = response.actionCalls.length > 0;
|
|
6337
|
+
}
|
|
6338
|
+
// Add assistant response to history
|
|
6339
|
+
const assistantMessage = {
|
|
6340
|
+
role: 'assistant',
|
|
6341
|
+
content: response.text,
|
|
6342
|
+
tool_calls: result.tool_calls,
|
|
6343
|
+
};
|
|
6344
|
+
this.history.push(assistantMessage);
|
|
6345
|
+
this.trimHistory();
|
|
6346
|
+
this.emit('response', response.text);
|
|
6347
|
+
if (response.hasActions) {
|
|
6348
|
+
this.emit('actions', response.actionCalls);
|
|
6349
|
+
}
|
|
6350
|
+
// Trigger reply prediction generation (fire and forget)
|
|
6351
|
+
this.triggerReplyPrediction();
|
|
6352
|
+
if (onComplete) {
|
|
6353
|
+
onComplete(response);
|
|
6354
|
+
}
|
|
6355
|
+
},
|
|
6356
|
+
});
|
|
6357
|
+
}
|
|
6358
|
+
catch (error) {
|
|
6359
|
+
this._isTalking = false;
|
|
6360
|
+
throw error;
|
|
6361
|
+
}
|
|
6108
6362
|
}
|
|
6109
|
-
// =====
|
|
6363
|
+
// ===== Action Results Reporting =====
|
|
6110
6364
|
/**
|
|
6111
|
-
*
|
|
6112
|
-
*
|
|
6365
|
+
* Report action results back to the conversation
|
|
6366
|
+
* Call this after executing actions to let the NPC know the results
|
|
6113
6367
|
*/
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
isCompacted: false,
|
|
6121
|
-
compactionCount: 0,
|
|
6368
|
+
reportActionResults(results) {
|
|
6369
|
+
for (const [callId, result] of Object.entries(results)) {
|
|
6370
|
+
this.history.push({
|
|
6371
|
+
role: 'tool',
|
|
6372
|
+
tool_call_id: callId,
|
|
6373
|
+
content: result,
|
|
6122
6374
|
});
|
|
6123
6375
|
}
|
|
6124
6376
|
}
|
|
6125
6377
|
/**
|
|
6126
|
-
*
|
|
6127
|
-
* @param npc The NPC client to unregister
|
|
6378
|
+
* Report a single action result
|
|
6128
6379
|
*/
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6380
|
+
reportActionResult(callId, result) {
|
|
6381
|
+
this.history.push({
|
|
6382
|
+
role: 'tool',
|
|
6383
|
+
tool_call_id: callId,
|
|
6384
|
+
content: result,
|
|
6385
|
+
});
|
|
6133
6386
|
}
|
|
6134
6387
|
/**
|
|
6135
|
-
*
|
|
6136
|
-
* Called after each Talk() exchange.
|
|
6137
|
-
* @param npc The NPC client that had a conversation
|
|
6388
|
+
* Parse tool arguments from JSON string
|
|
6138
6389
|
*/
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
return;
|
|
6142
|
-
|
|
6143
|
-
|
|
6390
|
+
parseToolArguments(args) {
|
|
6391
|
+
try {
|
|
6392
|
+
return JSON.parse(args);
|
|
6393
|
+
}
|
|
6394
|
+
catch (_a) {
|
|
6395
|
+
return {};
|
|
6144
6396
|
}
|
|
6145
|
-
const state = this.npcStates.get(npc);
|
|
6146
|
-
state.lastConversationTime = new Date();
|
|
6147
|
-
state.isCompacted = false; // Reset compaction flag on new conversation
|
|
6148
6397
|
}
|
|
6398
|
+
// ===== Conversation History Management =====
|
|
6149
6399
|
/**
|
|
6150
|
-
* Get
|
|
6400
|
+
* Get conversation history
|
|
6151
6401
|
*/
|
|
6152
|
-
|
|
6153
|
-
return
|
|
6402
|
+
getHistory() {
|
|
6403
|
+
return [...this.history];
|
|
6154
6404
|
}
|
|
6155
6405
|
/**
|
|
6156
|
-
* Get the
|
|
6406
|
+
* Get the number of messages in history
|
|
6157
6407
|
*/
|
|
6158
|
-
|
|
6159
|
-
return this.
|
|
6408
|
+
getHistoryLength() {
|
|
6409
|
+
return this.history.length;
|
|
6160
6410
|
}
|
|
6161
|
-
// ===== Auto Compaction =====
|
|
6162
6411
|
/**
|
|
6163
|
-
*
|
|
6164
|
-
*
|
|
6165
|
-
* @returns True if eligible for compaction
|
|
6412
|
+
* Clear conversation history.
|
|
6413
|
+
* The character design and memories will be preserved.
|
|
6166
6414
|
*/
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
const state = this.npcStates.get(npc);
|
|
6171
|
-
if (!state)
|
|
6172
|
-
return false;
|
|
6173
|
-
// Check if already compacted since last conversation
|
|
6174
|
-
if (state.isCompacted)
|
|
6175
|
-
return false;
|
|
6176
|
-
// Check message count
|
|
6177
|
-
const history = npc.getHistory();
|
|
6178
|
-
const nonSystemMessages = history.filter(m => m.role !== 'system').length;
|
|
6179
|
-
if (nonSystemMessages < this.config.autoCompactMinMessages)
|
|
6180
|
-
return false;
|
|
6181
|
-
// Check time since last conversation
|
|
6182
|
-
const timeSinceLastConversation = (Date.now() - state.lastConversationTime.getTime()) / 1000;
|
|
6183
|
-
if (timeSinceLastConversation < this.config.autoCompactTimeoutSeconds)
|
|
6184
|
-
return false;
|
|
6185
|
-
return true;
|
|
6415
|
+
clearHistory() {
|
|
6416
|
+
this.history = [];
|
|
6417
|
+
this.emit('history_cleared');
|
|
6186
6418
|
}
|
|
6187
6419
|
/**
|
|
6188
|
-
*
|
|
6189
|
-
*
|
|
6190
|
-
* @param npc The NPC to compact
|
|
6191
|
-
* @returns True if compaction succeeded
|
|
6420
|
+
* Revert the last exchange (user message and assistant response) from history.
|
|
6421
|
+
* @returns true if reverted, false if not enough history
|
|
6192
6422
|
*/
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
this.logger.error('Cannot compact: No chat client factory set. Call setChatClientFactory() first.');
|
|
6200
|
-
return false;
|
|
6201
|
-
}
|
|
6202
|
-
const history = npc.getHistory();
|
|
6203
|
-
const nonSystemMessages = history.filter(m => m.role !== 'system');
|
|
6204
|
-
if (nonSystemMessages.length < 2) {
|
|
6205
|
-
this.logger.info('Skipping compaction: not enough messages');
|
|
6206
|
-
return false;
|
|
6207
|
-
}
|
|
6208
|
-
try {
|
|
6209
|
-
this.logger.info(`Starting compaction (${nonSystemMessages.length} messages)`);
|
|
6210
|
-
// Build conversation text for summarization
|
|
6211
|
-
const conversationText = nonSystemMessages
|
|
6212
|
-
.map(m => `${m.role}: ${m.content}`)
|
|
6213
|
-
.join('\n');
|
|
6214
|
-
// Create summarization prompt
|
|
6215
|
-
const summaryPrompt = `Summarize the following conversation concisely. Focus on:
|
|
6216
|
-
1. Key topics discussed
|
|
6217
|
-
2. Important information exchanged
|
|
6218
|
-
3. Any decisions or commitments made
|
|
6219
|
-
4. The emotional tone
|
|
6220
|
-
|
|
6221
|
-
Keep the summary under 200 words. Write in third person.
|
|
6222
|
-
|
|
6223
|
-
Conversation:
|
|
6224
|
-
${conversationText}`;
|
|
6225
|
-
// Use chat client for summarization
|
|
6226
|
-
const chatClient = this.chatClientFactory();
|
|
6227
|
-
const result = await chatClient.textGeneration({
|
|
6228
|
-
messages: [{ role: 'user', content: summaryPrompt }],
|
|
6229
|
-
temperature: 0.5,
|
|
6230
|
-
model: this.config.fastModel || undefined,
|
|
6231
|
-
});
|
|
6232
|
-
if (!result.content) {
|
|
6233
|
-
const error = 'Empty response from summarization';
|
|
6234
|
-
this.logger.error(`Compaction failed: ${error}`);
|
|
6235
|
-
this.emit('compactionFailed', npc, error);
|
|
6236
|
-
return false;
|
|
6423
|
+
revertHistory() {
|
|
6424
|
+
let lastAssistantIndex = -1;
|
|
6425
|
+
let lastUserIndex = -1;
|
|
6426
|
+
for (let i = this.history.length - 1; i >= 0; i--) {
|
|
6427
|
+
if (this.history[i].role === 'assistant' && lastAssistantIndex === -1) {
|
|
6428
|
+
lastAssistantIndex = i;
|
|
6237
6429
|
}
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
// Update state
|
|
6242
|
-
const state = this.npcStates.get(npc);
|
|
6243
|
-
if (state) {
|
|
6244
|
-
state.isCompacted = true;
|
|
6245
|
-
state.compactionCount++;
|
|
6430
|
+
else if (this.history[i].role === 'user' && lastAssistantIndex !== -1 && lastUserIndex === -1) {
|
|
6431
|
+
lastUserIndex = i;
|
|
6432
|
+
break;
|
|
6246
6433
|
}
|
|
6247
|
-
this.logger.info(`Compaction completed. Summary: ${result.content.substring(0, 100)}...`);
|
|
6248
|
-
this.emit('npcCompacted', npc);
|
|
6249
|
-
return true;
|
|
6250
6434
|
}
|
|
6251
|
-
|
|
6252
|
-
|
|
6253
|
-
this.
|
|
6254
|
-
this.
|
|
6255
|
-
|
|
6435
|
+
if (lastAssistantIndex !== -1 && lastUserIndex !== -1) {
|
|
6436
|
+
// Remove in reverse order to maintain indices
|
|
6437
|
+
this.history.splice(lastAssistantIndex, 1);
|
|
6438
|
+
this.history.splice(lastUserIndex, 1);
|
|
6439
|
+
this.emit('history_reverted');
|
|
6440
|
+
return true;
|
|
6256
6441
|
}
|
|
6442
|
+
return false;
|
|
6257
6443
|
}
|
|
6258
6444
|
/**
|
|
6259
|
-
*
|
|
6260
|
-
* @
|
|
6445
|
+
* Revert (remove) the last N chat messages from history
|
|
6446
|
+
* @param count Number of messages to remove
|
|
6447
|
+
* @returns Number of messages actually removed
|
|
6261
6448
|
*/
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
if (eligibleNpcs.length === 0) {
|
|
6449
|
+
revertChatMessages(count) {
|
|
6450
|
+
if (count <= 0)
|
|
6265
6451
|
return 0;
|
|
6452
|
+
const messagesToRemove = Math.min(count, this.history.length);
|
|
6453
|
+
const originalCount = this.history.length;
|
|
6454
|
+
this.history = this.history.slice(0, -messagesToRemove);
|
|
6455
|
+
const actuallyRemoved = originalCount - this.history.length;
|
|
6456
|
+
if (actuallyRemoved > 0) {
|
|
6457
|
+
this.emit('history_reverted', actuallyRemoved);
|
|
6266
6458
|
}
|
|
6267
|
-
|
|
6268
|
-
let successCount = 0;
|
|
6269
|
-
for (const npc of eligibleNpcs) {
|
|
6270
|
-
const success = await this.compactConversation(npc);
|
|
6271
|
-
if (success)
|
|
6272
|
-
successCount++;
|
|
6273
|
-
}
|
|
6274
|
-
return successCount;
|
|
6459
|
+
return actuallyRemoved;
|
|
6275
6460
|
}
|
|
6276
|
-
// ===== Auto Compact Timer =====
|
|
6277
6461
|
/**
|
|
6278
|
-
*
|
|
6462
|
+
* Revert to a specific point in history
|
|
6463
|
+
* @deprecated Use revertHistory() or revertChatMessages() instead
|
|
6279
6464
|
*/
|
|
6280
|
-
|
|
6281
|
-
if (this.
|
|
6282
|
-
this.
|
|
6465
|
+
revertToMessage(index) {
|
|
6466
|
+
if (index >= 0 && index < this.history.length) {
|
|
6467
|
+
this.history = this.history.slice(0, index + 1);
|
|
6468
|
+
this.emit('history_reverted', index);
|
|
6283
6469
|
}
|
|
6284
|
-
this.autoCompactTimer = setInterval(() => {
|
|
6285
|
-
this.runAutoCompactCheck();
|
|
6286
|
-
}, this.config.autoCompactCheckInterval);
|
|
6287
6470
|
}
|
|
6288
6471
|
/**
|
|
6289
|
-
*
|
|
6472
|
+
* Append a message to history manually
|
|
6290
6473
|
*/
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
this.autoCompactTimer = null;
|
|
6295
|
-
}
|
|
6474
|
+
appendMessage(message) {
|
|
6475
|
+
this.history.push(message);
|
|
6476
|
+
this.trimHistory();
|
|
6296
6477
|
}
|
|
6297
6478
|
/**
|
|
6298
|
-
*
|
|
6479
|
+
* Alias for appendMessage (Unity SDK compatibility)
|
|
6299
6480
|
*/
|
|
6300
|
-
|
|
6301
|
-
if (!
|
|
6481
|
+
appendChatMessage(role, content) {
|
|
6482
|
+
if (!role || !content) {
|
|
6483
|
+
this.logger.warn('Role and content cannot be empty');
|
|
6302
6484
|
return;
|
|
6303
|
-
const eligibleNpcs = Array.from(this.npcStates.keys()).filter(npc => this.isEligibleForCompaction(npc));
|
|
6304
|
-
for (const npc of eligibleNpcs) {
|
|
6305
|
-
// Fire and forget - don't block
|
|
6306
|
-
this.compactConversation(npc).catch(err => {
|
|
6307
|
-
this.logger.error('Auto-compact error:', err);
|
|
6308
|
-
});
|
|
6309
6485
|
}
|
|
6486
|
+
this.appendMessage({ role: role, content });
|
|
6310
6487
|
}
|
|
6311
|
-
// ===== Lifecycle =====
|
|
6312
6488
|
/**
|
|
6313
|
-
*
|
|
6489
|
+
* Trim history to max length
|
|
6314
6490
|
*/
|
|
6315
|
-
|
|
6316
|
-
this.
|
|
6317
|
-
|
|
6491
|
+
trimHistory() {
|
|
6492
|
+
if (this.history.length > this.maxHistoryLength) {
|
|
6493
|
+
// Keep the most recent messages
|
|
6494
|
+
this.history = this.history.slice(-this.maxHistoryLength);
|
|
6495
|
+
}
|
|
6318
6496
|
}
|
|
6497
|
+
// ===== Save/Load =====
|
|
6319
6498
|
/**
|
|
6320
|
-
*
|
|
6499
|
+
* Save the current conversation history to a serializable format.
|
|
6500
|
+
* Includes characterDesign, memories, and history.
|
|
6321
6501
|
*/
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6502
|
+
saveHistory() {
|
|
6503
|
+
const saveData = {
|
|
6504
|
+
characterDesign: this.characterDesign,
|
|
6505
|
+
memories: Array.from(this.memories.entries()).map(([name, content]) => ({ name, content })),
|
|
6506
|
+
history: this.history,
|
|
6507
|
+
};
|
|
6508
|
+
return JSON.stringify(saveData);
|
|
6325
6509
|
}
|
|
6326
6510
|
/**
|
|
6327
|
-
*
|
|
6511
|
+
* Load conversation history from serialized data.
|
|
6512
|
+
* Restores characterDesign, memories, and history.
|
|
6328
6513
|
*/
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6514
|
+
loadHistory(saveData) {
|
|
6515
|
+
try {
|
|
6516
|
+
const data = JSON.parse(saveData);
|
|
6517
|
+
// Load character design (with backwards compatibility for old systemPrompt field)
|
|
6518
|
+
this.characterDesign = data.characterDesign || data.systemPrompt || this.characterDesign;
|
|
6519
|
+
// Load memories
|
|
6520
|
+
this.memories.clear();
|
|
6521
|
+
if (data.memories && Array.isArray(data.memories)) {
|
|
6522
|
+
for (const memory of data.memories) {
|
|
6523
|
+
if (memory.name && memory.content) {
|
|
6524
|
+
this.memories.set(memory.name, memory.content);
|
|
6525
|
+
}
|
|
6526
|
+
}
|
|
6527
|
+
}
|
|
6528
|
+
// Load history (skip system messages as they'll be rebuilt from characterDesign + memories)
|
|
6529
|
+
this.history = (data.history || []).filter(m => m.role !== 'system');
|
|
6530
|
+
this.emit('history_loaded');
|
|
6531
|
+
return true;
|
|
6532
|
+
}
|
|
6533
|
+
catch (error) {
|
|
6534
|
+
this.logger.error('Failed to load history:', error);
|
|
6535
|
+
return false;
|
|
6536
|
+
}
|
|
6334
6537
|
}
|
|
6335
6538
|
}
|
|
6336
|
-
AIContextManager._instance = null;
|
|
6337
|
-
/**
|
|
6338
|
-
* Default AIContextManager instance
|
|
6339
|
-
* Can be used as a global context manager
|
|
6340
|
-
*/
|
|
6341
|
-
const defaultContextManager = AIContextManager.getInstance();
|
|
6342
6539
|
|
|
6343
6540
|
/**
|
|
6344
6541
|
* Schema Library for managing JSON schemas for AI structured output generation
|
|
@@ -6697,20 +6894,20 @@ ${conversationText}`;
|
|
|
6697
6894
|
// Create indicator element
|
|
6698
6895
|
this.devTokenIndicator = document.createElement('div');
|
|
6699
6896
|
this.devTokenIndicator.textContent = 'DeveloperToken';
|
|
6700
|
-
this.devTokenIndicator.style.cssText = `
|
|
6701
|
-
position: fixed;
|
|
6702
|
-
top: 10px;
|
|
6703
|
-
left: 10px;
|
|
6704
|
-
background-color: #dc2626;
|
|
6705
|
-
color: white;
|
|
6706
|
-
padding: 4px 12px;
|
|
6707
|
-
border-radius: 4px;
|
|
6708
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
6709
|
-
font-size: 12px;
|
|
6710
|
-
font-weight: 600;
|
|
6711
|
-
z-index: 999999;
|
|
6712
|
-
pointer-events: none;
|
|
6713
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
6897
|
+
this.devTokenIndicator.style.cssText = `
|
|
6898
|
+
position: fixed;
|
|
6899
|
+
top: 10px;
|
|
6900
|
+
left: 10px;
|
|
6901
|
+
background-color: #dc2626;
|
|
6902
|
+
color: white;
|
|
6903
|
+
padding: 4px 12px;
|
|
6904
|
+
border-radius: 4px;
|
|
6905
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
6906
|
+
font-size: 12px;
|
|
6907
|
+
font-weight: 600;
|
|
6908
|
+
z-index: 999999;
|
|
6909
|
+
pointer-events: none;
|
|
6910
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
6714
6911
|
`;
|
|
6715
6912
|
document.body.appendChild(this.devTokenIndicator);
|
|
6716
6913
|
}
|
|
@@ -6873,7 +7070,7 @@ ${conversationText}`;
|
|
|
6873
7070
|
*/
|
|
6874
7071
|
setDebug(enabled) {
|
|
6875
7072
|
this.config.debug = enabled;
|
|
6876
|
-
Logger.setGlobalLevel(enabled ?
|
|
7073
|
+
Logger.setGlobalLevel(enabled ? LogLevel.DEBUG : LogLevel.WARN);
|
|
6877
7074
|
}
|
|
6878
7075
|
/**
|
|
6879
7076
|
* Configure the logging system
|
|
@@ -6895,7 +7092,7 @@ ${conversationText}`;
|
|
|
6895
7092
|
initializeLogging(config) {
|
|
6896
7093
|
// Handle legacy debug option for backwards compatibility
|
|
6897
7094
|
if (config.debug !== undefined && config.logging === undefined) {
|
|
6898
|
-
Logger.setGlobalLevel(config.debug ?
|
|
7095
|
+
Logger.setGlobalLevel(config.debug ? LogLevel.DEBUG : LogLevel.WARN);
|
|
6899
7096
|
}
|
|
6900
7097
|
// Apply new logging config
|
|
6901
7098
|
if (config.logging) {
|
|
@@ -7099,9 +7296,7 @@ ${conversationText}`;
|
|
|
7099
7296
|
*/
|
|
7100
7297
|
async validateToken(token, gameId) {
|
|
7101
7298
|
var _a, _b;
|
|
7102
|
-
const headers = {
|
|
7103
|
-
'Authorization': `Bearer ${token}`,
|
|
7104
|
-
};
|
|
7299
|
+
const headers = Object.assign({ 'Authorization': `Bearer ${token}` }, getSDKHeaders());
|
|
7105
7300
|
if (gameId) {
|
|
7106
7301
|
headers['X-Game-Id'] = gameId;
|
|
7107
7302
|
}
|
|
@@ -7131,9 +7326,7 @@ ${conversationText}`;
|
|
|
7131
7326
|
*/
|
|
7132
7327
|
async verifyToken(token, gameId) {
|
|
7133
7328
|
var _a, _b;
|
|
7134
|
-
const headers = {
|
|
7135
|
-
'Authorization': `Bearer ${token}`,
|
|
7136
|
-
};
|
|
7329
|
+
const headers = Object.assign({ 'Authorization': `Bearer ${token}` }, getSDKHeaders());
|
|
7137
7330
|
if (gameId) {
|
|
7138
7331
|
headers['X-Game-Id'] = gameId;
|
|
7139
7332
|
}
|
|
@@ -7196,36 +7389,61 @@ ${conversationText}`;
|
|
|
7196
7389
|
*/
|
|
7197
7390
|
const defaultTokenValidator = new TokenValidator();
|
|
7198
7391
|
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
|
|
7210
|
-
|
|
7211
|
-
|
|
7212
|
-
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
7392
|
+
/**
|
|
7393
|
+
* PlayKit SDK for JavaScript
|
|
7394
|
+
* AI integration for web-based games
|
|
7395
|
+
*/
|
|
7396
|
+
// Main SDK
|
|
7397
|
+
|
|
7398
|
+
var namespace = /*#__PURE__*/Object.freeze({
|
|
7399
|
+
__proto__: null,
|
|
7400
|
+
AIContextManager: AIContextManager,
|
|
7401
|
+
AuthFlowManager: AuthFlowManager,
|
|
7402
|
+
AuthManager: AuthManager,
|
|
7403
|
+
BrowserStorage: BrowserStorage,
|
|
7404
|
+
BufferLogHandler: BufferLogHandler,
|
|
7405
|
+
CallbackLogHandler: CallbackLogHandler,
|
|
7406
|
+
ChatClient: ChatClient,
|
|
7407
|
+
DeviceAuthFlowManager: DeviceAuthFlowManager,
|
|
7408
|
+
ImageClient: ImageClient,
|
|
7409
|
+
get LogLevel () { return LogLevel; },
|
|
7410
|
+
Logger: Logger,
|
|
7411
|
+
MemoryStorage: MemoryStorage,
|
|
7412
|
+
NPCClient: NPCClient,
|
|
7413
|
+
PlayKitSDK: PlayKitSDK,
|
|
7414
|
+
PlayerClient: PlayerClient,
|
|
7415
|
+
RechargeManager: RechargeManager,
|
|
7416
|
+
SchemaLibrary: SchemaLibrary,
|
|
7417
|
+
StreamParser: StreamParser,
|
|
7418
|
+
TokenStorage: TokenStorage,
|
|
7419
|
+
TokenValidator: TokenValidator,
|
|
7420
|
+
TranscriptionClient: TranscriptionClient,
|
|
7421
|
+
createMultimodalMessage: createMultimodalMessage,
|
|
7422
|
+
createStorage: createStorage,
|
|
7423
|
+
createTextMessage: createTextMessage,
|
|
7424
|
+
default: PlayKitSDK,
|
|
7425
|
+
defaultContextManager: defaultContextManager,
|
|
7426
|
+
defaultSchemaLibrary: defaultSchemaLibrary,
|
|
7427
|
+
defaultTokenValidator: defaultTokenValidator,
|
|
7428
|
+
isLocalStorageAvailable: isLocalStorageAvailable
|
|
7429
|
+
});
|
|
7430
|
+
|
|
7431
|
+
/**
|
|
7432
|
+
* UMD bundle entry.
|
|
7433
|
+
*
|
|
7434
|
+
* Goal: make `window.PlayKitSDK` directly the constructor class while still
|
|
7435
|
+
* exposing every named export as a property on it.
|
|
7436
|
+
*
|
|
7437
|
+
* Result:
|
|
7438
|
+
* new window.PlayKitSDK(cfg) // recommended
|
|
7439
|
+
* new window.PlayKitSDK.PlayKitSDK(cfg) // legacy v1.x form, still works
|
|
7440
|
+
* window.PlayKitSDK.ChatClient // named exports preserved
|
|
7441
|
+
*/
|
|
7442
|
+
Object.assign(PlayKitSDK, namespace);
|
|
7443
|
+
PlayKitSDK.PlayKitSDK = PlayKitSDK;
|
|
7444
|
+
PlayKitSDK.default = PlayKitSDK;
|
|
7445
|
+
|
|
7446
|
+
return PlayKitSDK;
|
|
7229
7447
|
|
|
7230
7448
|
}));
|
|
7231
7449
|
//# sourceMappingURL=playkit-sdk.umd.js.map
|