playkit-sdk 1.2.13 → 1.4.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +86 -86
- package/README.md +266 -244
- package/dist/playkit-sdk.cjs.js +1948 -1577
- package/dist/playkit-sdk.cjs.js.map +1 -1
- package/dist/playkit-sdk.d.ts +201 -7
- package/dist/playkit-sdk.esm.js +1948 -1578
- package/dist/playkit-sdk.esm.js.map +1 -1
- package/dist/playkit-sdk.umd.js +2042 -1646
- package/dist/playkit-sdk.umd.js.map +1 -1
- package/package.json +72 -70
package/dist/playkit-sdk.umd.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* playkit-sdk v1.
|
|
2
|
+
* playkit-sdk v1.4.0-beta.1
|
|
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.4.0-beta.1"';
|
|
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') {
|
|
@@ -2766,7 +2773,7 @@
|
|
|
2766
2773
|
* Handles JWT exchange and token management
|
|
2767
2774
|
*/
|
|
2768
2775
|
// @ts-ignore - replaced at build time
|
|
2769
|
-
const DEFAULT_BASE_URL$
|
|
2776
|
+
const DEFAULT_BASE_URL$6 = "https://api.playkit.ai";
|
|
2770
2777
|
const JWT_EXCHANGE_ENDPOINT = '/api/external/exchange-jwt';
|
|
2771
2778
|
const TOKEN_REFRESH_ENDPOINT = '/api/auth/refresh';
|
|
2772
2779
|
class AuthManager extends EventEmitter {
|
|
@@ -2784,7 +2791,7 @@
|
|
|
2784
2791
|
this.storage = new TokenStorage({
|
|
2785
2792
|
mode: config.mode === 'server' ? 'server' : 'browser',
|
|
2786
2793
|
});
|
|
2787
|
-
this.baseURL = config.baseURL || DEFAULT_BASE_URL$
|
|
2794
|
+
this.baseURL = config.baseURL || DEFAULT_BASE_URL$6;
|
|
2788
2795
|
this.authState = {
|
|
2789
2796
|
isAuthenticated: false,
|
|
2790
2797
|
};
|
|
@@ -2966,10 +2973,7 @@
|
|
|
2966
2973
|
try {
|
|
2967
2974
|
const response = await fetch(`${this.baseURL}${JWT_EXCHANGE_ENDPOINT}`, {
|
|
2968
2975
|
method: 'POST',
|
|
2969
|
-
headers: {
|
|
2970
|
-
Authorization: `Bearer ${jwt}`,
|
|
2971
|
-
'Content-Type': 'application/json',
|
|
2972
|
-
},
|
|
2976
|
+
headers: Object.assign({ Authorization: `Bearer ${jwt}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
2973
2977
|
body: JSON.stringify({ gameId: this.config.gameId }),
|
|
2974
2978
|
});
|
|
2975
2979
|
if (!response.ok) {
|
|
@@ -3319,9 +3323,7 @@
|
|
|
3319
3323
|
this.logger.debug('Refreshing access token');
|
|
3320
3324
|
const response = await fetch(`${this.baseURL}${TOKEN_REFRESH_ENDPOINT}`, {
|
|
3321
3325
|
method: 'POST',
|
|
3322
|
-
headers: {
|
|
3323
|
-
'Content-Type': 'application/json',
|
|
3324
|
-
},
|
|
3326
|
+
headers: Object.assign({ 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
3325
3327
|
body: JSON.stringify({
|
|
3326
3328
|
refresh_token: this.authState.refreshToken,
|
|
3327
3329
|
}),
|
|
@@ -3424,7 +3426,7 @@
|
|
|
3424
3426
|
* RechargeManager handles the recharge modal UI and recharge window opening
|
|
3425
3427
|
*/
|
|
3426
3428
|
class RechargeManager extends EventEmitter {
|
|
3427
|
-
constructor(playerToken, rechargePortalUrl = 'https://playkit.ai/recharge', gameId) {
|
|
3429
|
+
constructor(playerToken, rechargePortalUrl = 'https://players.playkit.ai/recharge', gameId) {
|
|
3428
3430
|
super();
|
|
3429
3431
|
this.modalContainer = null;
|
|
3430
3432
|
this.styleElement = null;
|
|
@@ -3527,220 +3529,220 @@
|
|
|
3527
3529
|
return;
|
|
3528
3530
|
}
|
|
3529
3531
|
this.styleElement = document.createElement('style');
|
|
3530
|
-
this.styleElement.textContent = `
|
|
3531
|
-
.playkit-recharge-overlay {
|
|
3532
|
-
position: fixed;
|
|
3533
|
-
top: 0;
|
|
3534
|
-
left: 0;
|
|
3535
|
-
right: 0;
|
|
3536
|
-
bottom: 0;
|
|
3537
|
-
background: rgba(0, 0, 0, 0.8);
|
|
3538
|
-
display: flex;
|
|
3539
|
-
justify-content: center;
|
|
3540
|
-
align-items: center;
|
|
3541
|
-
z-index: 999999;
|
|
3542
|
-
animation: playkit-recharge-fadeIn 0.2s ease-out;
|
|
3543
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3544
|
-
}
|
|
3545
|
-
|
|
3546
|
-
@keyframes playkit-recharge-fadeIn {
|
|
3547
|
-
from {
|
|
3548
|
-
opacity: 0;
|
|
3549
|
-
}
|
|
3550
|
-
to {
|
|
3551
|
-
opacity: 1;
|
|
3552
|
-
}
|
|
3553
|
-
}
|
|
3554
|
-
|
|
3555
|
-
.playkit-recharge-modal {
|
|
3556
|
-
background: #fff;
|
|
3557
|
-
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
3558
|
-
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
|
|
3559
|
-
padding: 24px;
|
|
3560
|
-
max-width: 320px;
|
|
3561
|
-
width: 90%;
|
|
3562
|
-
position: relative;
|
|
3563
|
-
text-align: center;
|
|
3564
|
-
}
|
|
3565
|
-
|
|
3566
|
-
.playkit-recharge-title {
|
|
3567
|
-
font-size: 14px;
|
|
3568
|
-
font-weight: 600;
|
|
3569
|
-
color: #171717;
|
|
3570
|
-
margin: 0 0 8px 0;
|
|
3571
|
-
text-align: center;
|
|
3572
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3573
|
-
}
|
|
3574
|
-
|
|
3575
|
-
.playkit-recharge-message {
|
|
3576
|
-
font-size: 14px;
|
|
3577
|
-
color: #666;
|
|
3578
|
-
margin: 0 0 20px 0;
|
|
3579
|
-
text-align: center;
|
|
3580
|
-
line-height: 1.5;
|
|
3581
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3582
|
-
}
|
|
3583
|
-
|
|
3584
|
-
.playkit-recharge-balance {
|
|
3585
|
-
background: #f5f5f5;
|
|
3586
|
-
border: 1px solid #e5e7eb;
|
|
3587
|
-
padding: 16px;
|
|
3588
|
-
margin: 0 0 20px 0;
|
|
3589
|
-
text-align: center;
|
|
3590
|
-
}
|
|
3591
|
-
|
|
3592
|
-
.playkit-recharge-balance-label {
|
|
3593
|
-
font-size: 12px;
|
|
3594
|
-
color: #666;
|
|
3595
|
-
margin: 0 0 8px 0;
|
|
3596
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3597
|
-
}
|
|
3598
|
-
|
|
3599
|
-
.playkit-recharge-balance-value {
|
|
3600
|
-
font-size: 24px;
|
|
3601
|
-
font-weight: bold;
|
|
3602
|
-
color: #171717;
|
|
3603
|
-
margin: 0;
|
|
3604
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3605
|
-
}
|
|
3606
|
-
|
|
3607
|
-
.playkit-recharge-balance-unit {
|
|
3608
|
-
font-size: 14px;
|
|
3609
|
-
color: #666;
|
|
3610
|
-
margin-left: 4px;
|
|
3611
|
-
}
|
|
3612
|
-
|
|
3613
|
-
.playkit-recharge-buttons {
|
|
3614
|
-
display: flex;
|
|
3615
|
-
flex-direction: column;
|
|
3616
|
-
gap: 8px;
|
|
3617
|
-
}
|
|
3618
|
-
|
|
3619
|
-
.playkit-recharge-button {
|
|
3620
|
-
width: 100%;
|
|
3621
|
-
padding: 10px 16px;
|
|
3622
|
-
border: none;
|
|
3623
|
-
font-size: 14px;
|
|
3624
|
-
font-weight: 500;
|
|
3625
|
-
cursor: pointer;
|
|
3626
|
-
transition: all 0.2s ease;
|
|
3627
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3628
|
-
}
|
|
3629
|
-
|
|
3630
|
-
.playkit-recharge-button-primary {
|
|
3631
|
-
background: #171717;
|
|
3632
|
-
color: white;
|
|
3633
|
-
}
|
|
3634
|
-
|
|
3635
|
-
.playkit-recharge-button-primary:hover {
|
|
3636
|
-
background: #404040;
|
|
3637
|
-
}
|
|
3638
|
-
|
|
3639
|
-
.playkit-recharge-button-primary:active {
|
|
3640
|
-
background: #0a0a0a;
|
|
3641
|
-
}
|
|
3642
|
-
|
|
3643
|
-
.playkit-recharge-button-secondary {
|
|
3644
|
-
background: transparent;
|
|
3645
|
-
color: #666;
|
|
3646
|
-
border: 1px solid #e5e7eb;
|
|
3647
|
-
}
|
|
3648
|
-
|
|
3649
|
-
.playkit-recharge-button-secondary:hover {
|
|
3650
|
-
background: #f5f5f5;
|
|
3651
|
-
border-color: #d4d4d4;
|
|
3652
|
-
}
|
|
3653
|
-
|
|
3654
|
-
.playkit-recharge-button-secondary:active {
|
|
3655
|
-
background: #e5e5e5;
|
|
3656
|
-
}
|
|
3657
|
-
|
|
3658
|
-
@media (max-width: 480px) {
|
|
3659
|
-
.playkit-recharge-modal {
|
|
3660
|
-
padding: 20px;
|
|
3661
|
-
}
|
|
3662
|
-
}
|
|
3663
|
-
|
|
3664
|
-
/* Daily Refresh Toast Styles */
|
|
3665
|
-
.playkit-daily-refresh-toast {
|
|
3666
|
-
position: fixed;
|
|
3667
|
-
top: 20px;
|
|
3668
|
-
right: 20px;
|
|
3669
|
-
background: #fff;
|
|
3670
|
-
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
3671
|
-
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.1);
|
|
3672
|
-
padding: 16px 20px;
|
|
3673
|
-
min-width: 240px;
|
|
3674
|
-
max-width: 320px;
|
|
3675
|
-
z-index: 999998;
|
|
3676
|
-
animation: playkit-toast-slideIn 0.3s ease-out;
|
|
3677
|
-
display: flex;
|
|
3678
|
-
align-items: flex-start;
|
|
3679
|
-
gap: 12px;
|
|
3680
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3681
|
-
}
|
|
3682
|
-
|
|
3683
|
-
.playkit-daily-refresh-toast.hiding {
|
|
3684
|
-
animation: playkit-toast-fadeOut 0.3s ease-out forwards;
|
|
3685
|
-
}
|
|
3686
|
-
|
|
3687
|
-
@keyframes playkit-toast-slideIn {
|
|
3688
|
-
from {
|
|
3689
|
-
transform: translateX(100%);
|
|
3690
|
-
opacity: 0;
|
|
3691
|
-
}
|
|
3692
|
-
to {
|
|
3693
|
-
transform: translateX(0);
|
|
3694
|
-
opacity: 1;
|
|
3695
|
-
}
|
|
3696
|
-
}
|
|
3697
|
-
|
|
3698
|
-
@keyframes playkit-toast-fadeOut {
|
|
3699
|
-
from {
|
|
3700
|
-
transform: translateX(0);
|
|
3701
|
-
opacity: 1;
|
|
3702
|
-
}
|
|
3703
|
-
to {
|
|
3704
|
-
transform: translateX(100%);
|
|
3705
|
-
opacity: 0;
|
|
3706
|
-
}
|
|
3707
|
-
}
|
|
3708
|
-
|
|
3709
|
-
.playkit-toast-icon {
|
|
3710
|
-
width: 24px;
|
|
3711
|
-
height: 24px;
|
|
3712
|
-
background: #171717;
|
|
3713
|
-
border-radius: 50%;
|
|
3714
|
-
display: flex;
|
|
3715
|
-
align-items: center;
|
|
3716
|
-
justify-content: center;
|
|
3717
|
-
flex-shrink: 0;
|
|
3718
|
-
}
|
|
3719
|
-
|
|
3720
|
-
.playkit-toast-icon svg {
|
|
3721
|
-
width: 14px;
|
|
3722
|
-
height: 14px;
|
|
3723
|
-
color: #ffffff;
|
|
3724
|
-
}
|
|
3725
|
-
|
|
3726
|
-
.playkit-toast-message {
|
|
3727
|
-
flex: 1;
|
|
3728
|
-
font-size: 14px;
|
|
3729
|
-
font-weight: 500;
|
|
3730
|
-
color: #171717;
|
|
3731
|
-
line-height: 1.4;
|
|
3732
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3733
|
-
}
|
|
3734
|
-
|
|
3735
|
-
@media (max-width: 480px) {
|
|
3736
|
-
.playkit-daily-refresh-toast {
|
|
3737
|
-
top: 10px;
|
|
3738
|
-
right: 10px;
|
|
3739
|
-
left: 10px;
|
|
3740
|
-
min-width: auto;
|
|
3741
|
-
max-width: none;
|
|
3742
|
-
}
|
|
3743
|
-
}
|
|
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
|
+
}
|
|
3744
3746
|
`;
|
|
3745
3747
|
document.head.appendChild(this.styleElement);
|
|
3746
3748
|
}
|
|
@@ -3877,7 +3879,8 @@
|
|
|
3877
3879
|
/**
|
|
3878
3880
|
* Player client for managing player information and credits
|
|
3879
3881
|
*/
|
|
3880
|
-
|
|
3882
|
+
// @ts-ignore - replaced at build time
|
|
3883
|
+
const DEFAULT_BASE_URL$5 = "https://api.playkit.ai";
|
|
3881
3884
|
const PLAYER_INFO_ENDPOINT = '/api/external/player-info';
|
|
3882
3885
|
const SET_NICKNAME_ENDPOINT = '/api/external/set-game-player-nickname';
|
|
3883
3886
|
class PlayerClient extends EventEmitter {
|
|
@@ -3889,13 +3892,13 @@
|
|
|
3889
3892
|
this.balanceCheckInterval = null;
|
|
3890
3893
|
this.logger = Logger.getLogger('PlayerClient');
|
|
3891
3894
|
this.authManager = authManager;
|
|
3892
|
-
this.baseURL = config.baseURL || DEFAULT_BASE_URL$
|
|
3895
|
+
this.baseURL = config.baseURL || DEFAULT_BASE_URL$5;
|
|
3893
3896
|
this.gameId = config.gameId;
|
|
3894
3897
|
this.rechargeConfig = {
|
|
3895
3898
|
autoShowBalanceModal: (_a = rechargeConfig.autoShowBalanceModal) !== null && _a !== void 0 ? _a : true,
|
|
3896
3899
|
balanceCheckInterval: (_b = rechargeConfig.balanceCheckInterval) !== null && _b !== void 0 ? _b : 30000,
|
|
3897
3900
|
checkBalanceAfterApiCall: (_c = rechargeConfig.checkBalanceAfterApiCall) !== null && _c !== void 0 ? _c : true,
|
|
3898
|
-
rechargePortalUrl: rechargeConfig.rechargePortalUrl || 'https://playkit.ai/recharge',
|
|
3901
|
+
rechargePortalUrl: rechargeConfig.rechargePortalUrl || 'https://players.playkit.ai/recharge',
|
|
3899
3902
|
showDailyRefreshToast: (_d = rechargeConfig.showDailyRefreshToast) !== null && _d !== void 0 ? _d : true,
|
|
3900
3903
|
};
|
|
3901
3904
|
}
|
|
@@ -3910,9 +3913,7 @@
|
|
|
3910
3913
|
}
|
|
3911
3914
|
try {
|
|
3912
3915
|
// Build headers with X-Game-Id to support Global Developer Token
|
|
3913
|
-
const headers = {
|
|
3914
|
-
Authorization: `Bearer ${token}`,
|
|
3915
|
-
};
|
|
3916
|
+
const headers = Object.assign({ Authorization: `Bearer ${token}` }, getSDKHeaders());
|
|
3916
3917
|
if (this.gameId) {
|
|
3917
3918
|
headers['X-Game-Id'] = this.gameId;
|
|
3918
3919
|
}
|
|
@@ -4012,10 +4013,7 @@
|
|
|
4012
4013
|
try {
|
|
4013
4014
|
const response = await fetch(`${this.baseURL}${SET_NICKNAME_ENDPOINT}`, {
|
|
4014
4015
|
method: 'POST',
|
|
4015
|
-
headers: {
|
|
4016
|
-
Authorization: `Bearer ${token}`,
|
|
4017
|
-
'Content-Type': 'application/json',
|
|
4018
|
-
},
|
|
4016
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
4019
4017
|
body: JSON.stringify({ nickname: trimmed }),
|
|
4020
4018
|
});
|
|
4021
4019
|
if (!response.ok) {
|
|
@@ -4165,15 +4163,89 @@
|
|
|
4165
4163
|
}
|
|
4166
4164
|
}
|
|
4167
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
|
+
|
|
4168
4227
|
/**
|
|
4169
4228
|
* Chat provider for HTTP communication with chat API
|
|
4170
4229
|
*/
|
|
4171
|
-
|
|
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$4 = "https://api.playkit.ai";
|
|
4172
4244
|
class ChatProvider {
|
|
4173
4245
|
constructor(authManager, config) {
|
|
4174
4246
|
this.authManager = authManager;
|
|
4175
4247
|
this.config = config;
|
|
4176
|
-
this.baseURL = config.baseURL || DEFAULT_BASE_URL$
|
|
4248
|
+
this.baseURL = config.baseURL || DEFAULT_BASE_URL$4;
|
|
4177
4249
|
}
|
|
4178
4250
|
/**
|
|
4179
4251
|
* Set player client for balance checking
|
|
@@ -4186,6 +4258,7 @@
|
|
|
4186
4258
|
*/
|
|
4187
4259
|
async chatCompletion(chatConfig) {
|
|
4188
4260
|
var _a;
|
|
4261
|
+
assertValidMessages(chatConfig.messages);
|
|
4189
4262
|
// Ensure token is valid, auto-refresh if needed (browser mode only)
|
|
4190
4263
|
await this.authManager.ensureValidToken();
|
|
4191
4264
|
const token = this.authManager.getToken();
|
|
@@ -4207,10 +4280,7 @@
|
|
|
4207
4280
|
try {
|
|
4208
4281
|
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
4209
4282
|
method: 'POST',
|
|
4210
|
-
headers: {
|
|
4211
|
-
Authorization: `Bearer ${token}`,
|
|
4212
|
-
'Content-Type': 'application/json',
|
|
4213
|
-
},
|
|
4283
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
4214
4284
|
body: JSON.stringify(requestBody),
|
|
4215
4285
|
});
|
|
4216
4286
|
if (!response.ok) {
|
|
@@ -4245,6 +4315,7 @@
|
|
|
4245
4315
|
*/
|
|
4246
4316
|
async chatCompletionStream(chatConfig) {
|
|
4247
4317
|
var _a;
|
|
4318
|
+
assertValidMessages(chatConfig.messages);
|
|
4248
4319
|
// Ensure token is valid, auto-refresh if needed (browser mode only)
|
|
4249
4320
|
await this.authManager.ensureValidToken();
|
|
4250
4321
|
const token = this.authManager.getToken();
|
|
@@ -4266,10 +4337,7 @@
|
|
|
4266
4337
|
try {
|
|
4267
4338
|
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
4268
4339
|
method: 'POST',
|
|
4269
|
-
headers: {
|
|
4270
|
-
Authorization: `Bearer ${token}`,
|
|
4271
|
-
'Content-Type': 'application/json',
|
|
4272
|
-
},
|
|
4340
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
4273
4341
|
body: JSON.stringify(requestBody),
|
|
4274
4342
|
});
|
|
4275
4343
|
if (!response.ok) {
|
|
@@ -4306,6 +4374,7 @@
|
|
|
4306
4374
|
*/
|
|
4307
4375
|
async chatCompletionWithTools(chatConfig) {
|
|
4308
4376
|
var _a, _b;
|
|
4377
|
+
assertValidMessages(chatConfig.messages);
|
|
4309
4378
|
const token = this.authManager.getToken();
|
|
4310
4379
|
if (!token) {
|
|
4311
4380
|
throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
|
|
@@ -4332,10 +4401,7 @@
|
|
|
4332
4401
|
try {
|
|
4333
4402
|
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
4334
4403
|
method: 'POST',
|
|
4335
|
-
headers: {
|
|
4336
|
-
Authorization: `Bearer ${token}`,
|
|
4337
|
-
'Content-Type': 'application/json',
|
|
4338
|
-
},
|
|
4404
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
4339
4405
|
body: JSON.stringify(requestBody),
|
|
4340
4406
|
});
|
|
4341
4407
|
if (!response.ok) {
|
|
@@ -4366,6 +4432,7 @@
|
|
|
4366
4432
|
*/
|
|
4367
4433
|
async chatCompletionWithToolsStream(chatConfig) {
|
|
4368
4434
|
var _a, _b;
|
|
4435
|
+
assertValidMessages(chatConfig.messages);
|
|
4369
4436
|
const token = this.authManager.getToken();
|
|
4370
4437
|
if (!token) {
|
|
4371
4438
|
throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
|
|
@@ -4392,10 +4459,7 @@
|
|
|
4392
4459
|
try {
|
|
4393
4460
|
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
4394
4461
|
method: 'POST',
|
|
4395
|
-
headers: {
|
|
4396
|
-
Authorization: `Bearer ${token}`,
|
|
4397
|
-
'Content-Type': 'application/json',
|
|
4398
|
-
},
|
|
4462
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
4399
4463
|
body: JSON.stringify(requestBody),
|
|
4400
4464
|
});
|
|
4401
4465
|
if (!response.ok) {
|
|
@@ -4465,10 +4529,7 @@
|
|
|
4465
4529
|
try {
|
|
4466
4530
|
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
4467
4531
|
method: 'POST',
|
|
4468
|
-
headers: {
|
|
4469
|
-
Authorization: `Bearer ${token}`,
|
|
4470
|
-
'Content-Type': 'application/json',
|
|
4471
|
-
},
|
|
4532
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
4472
4533
|
body: JSON.stringify(requestBody),
|
|
4473
4534
|
});
|
|
4474
4535
|
if (!response.ok) {
|
|
@@ -4486,11 +4547,12 @@
|
|
|
4486
4547
|
this.playerClient.checkBalanceAfterApiCall().catch(() => { });
|
|
4487
4548
|
}
|
|
4488
4549
|
// Parse the response content as JSON
|
|
4489
|
-
const
|
|
4490
|
-
if (!
|
|
4550
|
+
const rawContent = (_a = result.choices[0]) === null || _a === void 0 ? void 0 : _a.message.content;
|
|
4551
|
+
if (!rawContent) {
|
|
4491
4552
|
throw new PlayKitError('No content in response', 'NO_CONTENT');
|
|
4492
4553
|
}
|
|
4493
4554
|
try {
|
|
4555
|
+
const content = contentToString$1(rawContent);
|
|
4494
4556
|
return JSON.parse(content);
|
|
4495
4557
|
}
|
|
4496
4558
|
catch (parseError) {
|
|
@@ -4509,12 +4571,13 @@
|
|
|
4509
4571
|
/**
|
|
4510
4572
|
* Image generation provider for HTTP communication with image API
|
|
4511
4573
|
*/
|
|
4512
|
-
|
|
4574
|
+
// @ts-ignore - replaced at build time
|
|
4575
|
+
const DEFAULT_BASE_URL$3 = "https://api.playkit.ai";
|
|
4513
4576
|
class ImageProvider {
|
|
4514
4577
|
constructor(authManager, config) {
|
|
4515
4578
|
this.authManager = authManager;
|
|
4516
4579
|
this.config = config;
|
|
4517
|
-
this.baseURL = config.baseURL || DEFAULT_BASE_URL$
|
|
4580
|
+
this.baseURL = config.baseURL || DEFAULT_BASE_URL$3;
|
|
4518
4581
|
}
|
|
4519
4582
|
/**
|
|
4520
4583
|
* Set player client for balance checking
|
|
@@ -4562,10 +4625,7 @@
|
|
|
4562
4625
|
try {
|
|
4563
4626
|
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
4564
4627
|
method: 'POST',
|
|
4565
|
-
headers: {
|
|
4566
|
-
Authorization: `Bearer ${token}`,
|
|
4567
|
-
'Content-Type': 'application/json',
|
|
4568
|
-
},
|
|
4628
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
4569
4629
|
body: JSON.stringify(requestBody),
|
|
4570
4630
|
});
|
|
4571
4631
|
if (!response.ok) {
|
|
@@ -4600,12 +4660,13 @@
|
|
|
4600
4660
|
/**
|
|
4601
4661
|
* Transcription provider for HTTP communication with audio transcription API
|
|
4602
4662
|
*/
|
|
4603
|
-
|
|
4663
|
+
// @ts-ignore - replaced at build time
|
|
4664
|
+
const DEFAULT_BASE_URL$2 = "https://api.playkit.ai";
|
|
4604
4665
|
class TranscriptionProvider {
|
|
4605
4666
|
constructor(authManager, config) {
|
|
4606
4667
|
this.authManager = authManager;
|
|
4607
4668
|
this.config = config;
|
|
4608
|
-
this.baseURL = config.baseURL || DEFAULT_BASE_URL$
|
|
4669
|
+
this.baseURL = config.baseURL || DEFAULT_BASE_URL$2;
|
|
4609
4670
|
}
|
|
4610
4671
|
/**
|
|
4611
4672
|
* Set player client for balance checking
|
|
@@ -4665,10 +4726,7 @@
|
|
|
4665
4726
|
try {
|
|
4666
4727
|
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
4667
4728
|
method: 'POST',
|
|
4668
|
-
headers: {
|
|
4669
|
-
Authorization: `Bearer ${token}`,
|
|
4670
|
-
'Content-Type': 'application/json',
|
|
4671
|
-
},
|
|
4729
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
4672
4730
|
body: JSON.stringify(requestBody),
|
|
4673
4731
|
});
|
|
4674
4732
|
if (!response.ok) {
|
|
@@ -4702,73 +4760,183 @@
|
|
|
4702
4760
|
}
|
|
4703
4761
|
}
|
|
4704
4762
|
|
|
4705
|
-
/******************************************************************************
|
|
4706
|
-
Copyright (c) Microsoft Corporation.
|
|
4707
|
-
|
|
4708
|
-
Permission to use, copy, modify, and/or distribute this software for any
|
|
4709
|
-
purpose with or without fee is hereby granted.
|
|
4710
|
-
|
|
4711
|
-
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
4712
|
-
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
4713
|
-
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
4714
|
-
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
4715
|
-
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
4716
|
-
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
4717
|
-
PERFORMANCE OF THIS SOFTWARE.
|
|
4718
|
-
***************************************************************************** */
|
|
4719
|
-
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
function __values(o) {
|
|
4723
|
-
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
4724
|
-
if (m) return m.call(o);
|
|
4725
|
-
if (o && typeof o.length === "number") return {
|
|
4726
|
-
next: function () {
|
|
4727
|
-
if (o && i >= o.length) o = void 0;
|
|
4728
|
-
return { value: o && o[i++], done: !o };
|
|
4729
|
-
}
|
|
4730
|
-
};
|
|
4731
|
-
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
4732
|
-
}
|
|
4733
|
-
|
|
4734
|
-
function __await(v) {
|
|
4735
|
-
return this instanceof __await ? (this.v = v, this) : new __await(v);
|
|
4736
|
-
}
|
|
4737
|
-
|
|
4738
|
-
function __asyncGenerator(thisArg, _arguments, generator) {
|
|
4739
|
-
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
4740
|
-
var g = generator.apply(thisArg, _arguments || []), i, q = [];
|
|
4741
|
-
return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
|
|
4742
|
-
function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
|
|
4743
|
-
function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
|
|
4744
|
-
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
|
|
4745
|
-
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
|
|
4746
|
-
function fulfill(value) { resume("next", value); }
|
|
4747
|
-
function reject(value) { resume("throw", value); }
|
|
4748
|
-
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
|
|
4749
|
-
}
|
|
4750
|
-
|
|
4751
|
-
function __asyncValues(o) {
|
|
4752
|
-
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
4753
|
-
var m = o[Symbol.asyncIterator], i;
|
|
4754
|
-
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
|
4755
|
-
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
4756
|
-
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
4757
|
-
}
|
|
4758
|
-
|
|
4759
|
-
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
4760
|
-
var e = new Error(message);
|
|
4761
|
-
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
4762
|
-
};
|
|
4763
|
-
|
|
4764
|
-
/**
|
|
4765
|
-
* Server-Sent Events (SSE) stream parser
|
|
4766
|
-
* Handles parsing of streaming text responses
|
|
4767
|
-
*/
|
|
4768
4763
|
/**
|
|
4769
|
-
*
|
|
4764
|
+
* TTS provider for HTTP communication with the text-to-speech API
|
|
4770
4765
|
*/
|
|
4771
|
-
|
|
4766
|
+
// @ts-ignore - replaced at build time
|
|
4767
|
+
const DEFAULT_BASE_URL$1 = "https://api.playkit.ai";
|
|
4768
|
+
class TTSProvider {
|
|
4769
|
+
constructor(authManager, config) {
|
|
4770
|
+
this.authManager = authManager;
|
|
4771
|
+
this.config = config;
|
|
4772
|
+
this.baseURL = config.baseURL || DEFAULT_BASE_URL$1;
|
|
4773
|
+
}
|
|
4774
|
+
/**
|
|
4775
|
+
* Set player client for balance checking
|
|
4776
|
+
*/
|
|
4777
|
+
setPlayerClient(playerClient) {
|
|
4778
|
+
this.playerClient = playerClient;
|
|
4779
|
+
}
|
|
4780
|
+
/**
|
|
4781
|
+
* Synthesize text into speech audio
|
|
4782
|
+
*/
|
|
4783
|
+
async synthesize(ttsConfig) {
|
|
4784
|
+
// Ensure token is valid, auto-refresh if needed (browser mode only)
|
|
4785
|
+
await this.authManager.ensureValidToken();
|
|
4786
|
+
const token = this.authManager.getToken();
|
|
4787
|
+
if (!token) {
|
|
4788
|
+
throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
|
|
4789
|
+
}
|
|
4790
|
+
const model = ttsConfig.model || this.config.defaultTTSModel || 'default-tts-model';
|
|
4791
|
+
const endpoint = `/ai/${this.config.gameId}/v2/audio/speech`;
|
|
4792
|
+
const requestBody = {
|
|
4793
|
+
model,
|
|
4794
|
+
text: ttsConfig.text,
|
|
4795
|
+
};
|
|
4796
|
+
// Add optional parameters (only when defined)
|
|
4797
|
+
if (ttsConfig.voice !== undefined) {
|
|
4798
|
+
requestBody.voice = ttsConfig.voice;
|
|
4799
|
+
}
|
|
4800
|
+
if (ttsConfig.speed !== undefined) {
|
|
4801
|
+
requestBody.speed = ttsConfig.speed;
|
|
4802
|
+
}
|
|
4803
|
+
if (ttsConfig.vol !== undefined) {
|
|
4804
|
+
requestBody.vol = ttsConfig.vol;
|
|
4805
|
+
}
|
|
4806
|
+
if (ttsConfig.pitch !== undefined) {
|
|
4807
|
+
requestBody.pitch = ttsConfig.pitch;
|
|
4808
|
+
}
|
|
4809
|
+
if (ttsConfig.emotion !== undefined) {
|
|
4810
|
+
requestBody.emotion = ttsConfig.emotion;
|
|
4811
|
+
}
|
|
4812
|
+
if (ttsConfig.languageBoost !== undefined) {
|
|
4813
|
+
requestBody.language_boost = ttsConfig.languageBoost;
|
|
4814
|
+
}
|
|
4815
|
+
if (ttsConfig.format !== undefined) {
|
|
4816
|
+
requestBody.response_format = ttsConfig.format;
|
|
4817
|
+
}
|
|
4818
|
+
if (ttsConfig.voiceSetting !== undefined) {
|
|
4819
|
+
requestBody.voice_setting = ttsConfig.voiceSetting;
|
|
4820
|
+
}
|
|
4821
|
+
if (ttsConfig.audioSetting !== undefined) {
|
|
4822
|
+
requestBody.audio_setting = ttsConfig.audioSetting;
|
|
4823
|
+
}
|
|
4824
|
+
try {
|
|
4825
|
+
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
4826
|
+
method: 'POST',
|
|
4827
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
4828
|
+
body: JSON.stringify(requestBody),
|
|
4829
|
+
});
|
|
4830
|
+
if (!response.ok) {
|
|
4831
|
+
const error = await response.json().catch(() => ({ message: 'Speech synthesis failed' }));
|
|
4832
|
+
const playKitError = new PlayKitError(error.message || 'Speech synthesis failed', error.code, response.status);
|
|
4833
|
+
// Check for insufficient credits error
|
|
4834
|
+
if (error.code === 'INSUFFICIENT_CREDITS' ||
|
|
4835
|
+
error.code === 'PLAYER_INSUFFICIENT_CREDIT' ||
|
|
4836
|
+
response.status === 402) {
|
|
4837
|
+
if (this.playerClient) {
|
|
4838
|
+
await this.playerClient.handleInsufficientCredits(playKitError);
|
|
4839
|
+
}
|
|
4840
|
+
}
|
|
4841
|
+
throw playKitError;
|
|
4842
|
+
}
|
|
4843
|
+
// SUCCESS: response is raw audio bytes, NOT JSON.
|
|
4844
|
+
const audio = await response.arrayBuffer();
|
|
4845
|
+
const contentType = response.headers.get('Content-Type');
|
|
4846
|
+
const usageHeader = response.headers.get('X-Usage-Characters');
|
|
4847
|
+
const audioLengthHeader = response.headers.get('X-Audio-Length-Ms');
|
|
4848
|
+
const result = {
|
|
4849
|
+
audio,
|
|
4850
|
+
format: contentType || ttsConfig.format || 'mp3',
|
|
4851
|
+
usageCharacters: Number(usageHeader) || 0,
|
|
4852
|
+
};
|
|
4853
|
+
if (audioLengthHeader !== null) {
|
|
4854
|
+
result.audioLengthMs = Number(audioLengthHeader) || 0;
|
|
4855
|
+
}
|
|
4856
|
+
// Check balance after successful API call
|
|
4857
|
+
if (this.playerClient) {
|
|
4858
|
+
this.playerClient.checkBalanceAfterApiCall().catch(() => {
|
|
4859
|
+
// Silently fail
|
|
4860
|
+
});
|
|
4861
|
+
}
|
|
4862
|
+
return result;
|
|
4863
|
+
}
|
|
4864
|
+
catch (error) {
|
|
4865
|
+
if (error instanceof PlayKitError) {
|
|
4866
|
+
throw error;
|
|
4867
|
+
}
|
|
4868
|
+
throw new PlayKitError(error instanceof Error ? error.message : 'Unknown error', 'TTS_ERROR');
|
|
4869
|
+
}
|
|
4870
|
+
}
|
|
4871
|
+
}
|
|
4872
|
+
|
|
4873
|
+
/******************************************************************************
|
|
4874
|
+
Copyright (c) Microsoft Corporation.
|
|
4875
|
+
|
|
4876
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
4877
|
+
purpose with or without fee is hereby granted.
|
|
4878
|
+
|
|
4879
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
4880
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
4881
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
4882
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
4883
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
4884
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
4885
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
4886
|
+
***************************************************************************** */
|
|
4887
|
+
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
4888
|
+
|
|
4889
|
+
|
|
4890
|
+
function __values(o) {
|
|
4891
|
+
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
4892
|
+
if (m) return m.call(o);
|
|
4893
|
+
if (o && typeof o.length === "number") return {
|
|
4894
|
+
next: function () {
|
|
4895
|
+
if (o && i >= o.length) o = void 0;
|
|
4896
|
+
return { value: o && o[i++], done: !o };
|
|
4897
|
+
}
|
|
4898
|
+
};
|
|
4899
|
+
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
4900
|
+
}
|
|
4901
|
+
|
|
4902
|
+
function __await(v) {
|
|
4903
|
+
return this instanceof __await ? (this.v = v, this) : new __await(v);
|
|
4904
|
+
}
|
|
4905
|
+
|
|
4906
|
+
function __asyncGenerator(thisArg, _arguments, generator) {
|
|
4907
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
4908
|
+
var g = generator.apply(thisArg, _arguments || []), i, q = [];
|
|
4909
|
+
return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
|
|
4910
|
+
function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
|
|
4911
|
+
function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
|
|
4912
|
+
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
|
|
4913
|
+
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
|
|
4914
|
+
function fulfill(value) { resume("next", value); }
|
|
4915
|
+
function reject(value) { resume("throw", value); }
|
|
4916
|
+
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
|
|
4917
|
+
}
|
|
4918
|
+
|
|
4919
|
+
function __asyncValues(o) {
|
|
4920
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
4921
|
+
var m = o[Symbol.asyncIterator], i;
|
|
4922
|
+
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
|
4923
|
+
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
4924
|
+
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
4925
|
+
}
|
|
4926
|
+
|
|
4927
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
4928
|
+
var e = new Error(message);
|
|
4929
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
4930
|
+
};
|
|
4931
|
+
|
|
4932
|
+
/**
|
|
4933
|
+
* Server-Sent Events (SSE) stream parser
|
|
4934
|
+
* Handles parsing of streaming text responses
|
|
4935
|
+
*/
|
|
4936
|
+
/**
|
|
4937
|
+
* Create a cross-platform text decoder
|
|
4938
|
+
*/
|
|
4939
|
+
function createDecoder() {
|
|
4772
4940
|
if (typeof TextDecoder !== 'undefined') {
|
|
4773
4941
|
return new TextDecoder();
|
|
4774
4942
|
}
|
|
@@ -4814,9 +4982,18 @@
|
|
|
4814
4982
|
if (text) {
|
|
4815
4983
|
yield yield __await(text);
|
|
4816
4984
|
}
|
|
4817
|
-
|
|
4985
|
+
// Stream termination events
|
|
4986
|
+
if (parsed.type === 'done' || parsed.type === 'finish' || parsed.finish_reason) {
|
|
4987
|
+
return yield __await(void 0);
|
|
4988
|
+
}
|
|
4989
|
+
if (parsed.type === 'abort') {
|
|
4990
|
+
// Server-side timeout or cancellation — treat as end of stream
|
|
4818
4991
|
return yield __await(void 0);
|
|
4819
4992
|
}
|
|
4993
|
+
if (parsed.type === 'error') {
|
|
4994
|
+
// Server-side error event — throw to trigger onError callback
|
|
4995
|
+
throw new Error(parsed.errorText || parsed.error || 'Stream error');
|
|
4996
|
+
}
|
|
4820
4997
|
}
|
|
4821
4998
|
catch (error) {
|
|
4822
4999
|
// If JSON parse fails, treat as plain text
|
|
@@ -4915,6 +5092,18 @@
|
|
|
4915
5092
|
/**
|
|
4916
5093
|
* Chat client for AI text generation
|
|
4917
5094
|
*/
|
|
5095
|
+
/**
|
|
5096
|
+
* Helper to extract string from MessageContent
|
|
5097
|
+
*/
|
|
5098
|
+
function contentToString(content) {
|
|
5099
|
+
if (!content)
|
|
5100
|
+
return '';
|
|
5101
|
+
if (typeof content === 'string')
|
|
5102
|
+
return content;
|
|
5103
|
+
// For array of content parts, extract text parts
|
|
5104
|
+
const textParts = content.filter(part => part.type === 'text');
|
|
5105
|
+
return textParts.map(part => part.text).join('');
|
|
5106
|
+
}
|
|
4918
5107
|
class ChatClient {
|
|
4919
5108
|
constructor(provider, model) {
|
|
4920
5109
|
this.schemaLibrary = null;
|
|
@@ -4964,7 +5153,7 @@
|
|
|
4964
5153
|
throw new Error('No choices in response');
|
|
4965
5154
|
}
|
|
4966
5155
|
return {
|
|
4967
|
-
content: choice.message.content,
|
|
5156
|
+
content: contentToString(choice.message.content),
|
|
4968
5157
|
model: response.model,
|
|
4969
5158
|
finishReason: choice.finish_reason,
|
|
4970
5159
|
usage: response.usage
|
|
@@ -5035,9 +5224,10 @@
|
|
|
5035
5224
|
}
|
|
5036
5225
|
// Extract user message content from the last user message
|
|
5037
5226
|
const lastUserMessage = [...messages].reverse().find(m => m.role === 'user');
|
|
5038
|
-
const prompt = (lastUserMessage === null || lastUserMessage === void 0 ? void 0 : lastUserMessage.content)
|
|
5227
|
+
const prompt = contentToString(lastUserMessage === null || lastUserMessage === void 0 ? void 0 : lastUserMessage.content);
|
|
5039
5228
|
// Build system message from messages array
|
|
5040
|
-
const
|
|
5229
|
+
const systemMessageContent = (_a = messages.find(m => m.role === 'system')) === null || _a === void 0 ? void 0 : _a.content;
|
|
5230
|
+
const systemMessage = contentToString(systemMessageContent) || undefined;
|
|
5041
5231
|
return this.generateStructuredWithSchema(schemaEntry.schema, prompt, Object.assign({ schemaName, schemaDescription: schemaEntry.description, systemMessage }, options));
|
|
5042
5232
|
}
|
|
5043
5233
|
/**
|
|
@@ -5128,7 +5318,7 @@
|
|
|
5128
5318
|
throw new Error('No choices in response');
|
|
5129
5319
|
}
|
|
5130
5320
|
return {
|
|
5131
|
-
content: choice.message.content
|
|
5321
|
+
content: contentToString(choice.message.content),
|
|
5132
5322
|
model: response.model,
|
|
5133
5323
|
finishReason: choice.finish_reason,
|
|
5134
5324
|
usage: response.usage
|
|
@@ -5192,7 +5382,7 @@
|
|
|
5192
5382
|
return new Promise((resolve, reject) => {
|
|
5193
5383
|
const img = new Image();
|
|
5194
5384
|
img.onload = () => resolve(img);
|
|
5195
|
-
img.onerror = (
|
|
5385
|
+
img.onerror = (_e) => reject(new Error('Failed to load image'));
|
|
5196
5386
|
img.src = this.toDataURL();
|
|
5197
5387
|
});
|
|
5198
5388
|
}
|
|
@@ -5206,13 +5396,14 @@
|
|
|
5206
5396
|
* Generate a single image
|
|
5207
5397
|
*/
|
|
5208
5398
|
async generateImage(config) {
|
|
5399
|
+
var _a;
|
|
5209
5400
|
const imageConfig = Object.assign(Object.assign({}, config), { model: config.model || this.model, n: 1 });
|
|
5210
5401
|
const response = await this.provider.generateImages(imageConfig);
|
|
5211
5402
|
const imageData = response.data[0];
|
|
5212
5403
|
if (!imageData || !imageData.b64_json) {
|
|
5213
5404
|
throw new Error('No image data in response');
|
|
5214
5405
|
}
|
|
5215
|
-
return new GeneratedImageImpl(imageData.b64_json, config.prompt, imageData.revised_prompt, config.size, imageData.b64_json_original, imageData.transparent_success);
|
|
5406
|
+
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);
|
|
5216
5407
|
}
|
|
5217
5408
|
/**
|
|
5218
5409
|
* Generate multiple images
|
|
@@ -5221,10 +5412,11 @@
|
|
|
5221
5412
|
const imageConfig = Object.assign(Object.assign({}, config), { model: config.model || this.model, n: config.n || 1 });
|
|
5222
5413
|
const response = await this.provider.generateImages(imageConfig);
|
|
5223
5414
|
return response.data.map((imageData) => {
|
|
5415
|
+
var _a;
|
|
5224
5416
|
if (!imageData.b64_json) {
|
|
5225
5417
|
throw new Error('No image data in response');
|
|
5226
5418
|
}
|
|
5227
|
-
return new GeneratedImageImpl(imageData.b64_json, config.prompt, imageData.revised_prompt, config.size, imageData.b64_json_original, imageData.transparent_success);
|
|
5419
|
+
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);
|
|
5228
5420
|
});
|
|
5229
5421
|
}
|
|
5230
5422
|
/**
|
|
@@ -5343,1019 +5535,1191 @@
|
|
|
5343
5535
|
}
|
|
5344
5536
|
|
|
5345
5537
|
/**
|
|
5346
|
-
*
|
|
5347
|
-
* Automatically handles conversation history
|
|
5348
|
-
*
|
|
5349
|
-
* Key Features:
|
|
5350
|
-
* - Call talk() for all interactions - actions are handled automatically
|
|
5351
|
-
* - Memory system for persistent NPC context
|
|
5352
|
-
* - Reply prediction for suggesting player responses
|
|
5353
|
-
* - Automatic conversation history management
|
|
5538
|
+
* High-level client for text-to-speech synthesis
|
|
5354
5539
|
*/
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5540
|
+
/**
|
|
5541
|
+
* Resolve an audio format/content-type string into a valid MIME type.
|
|
5542
|
+
* The provider's `result.format` may be a full MIME (e.g. 'audio/mpeg' from
|
|
5543
|
+
* the Content-Type header) or a bare token (e.g. 'mp3' from the fallback).
|
|
5544
|
+
* Full MIME strings pass through; bare tokens are mapped.
|
|
5545
|
+
*/
|
|
5546
|
+
function contentTypeFor(format) {
|
|
5547
|
+
if (format.includes('/')) {
|
|
5548
|
+
return format;
|
|
5549
|
+
}
|
|
5550
|
+
switch (format.toLowerCase()) {
|
|
5551
|
+
case 'mp3':
|
|
5552
|
+
return 'audio/mpeg';
|
|
5553
|
+
case 'wav':
|
|
5554
|
+
return 'audio/wav';
|
|
5555
|
+
case 'ogg':
|
|
5556
|
+
return 'audio/ogg';
|
|
5557
|
+
case 'flac':
|
|
5558
|
+
return 'audio/flac';
|
|
5559
|
+
case 'aac':
|
|
5560
|
+
return 'audio/aac';
|
|
5561
|
+
case 'pcm':
|
|
5562
|
+
return 'audio/pcm';
|
|
5563
|
+
default:
|
|
5564
|
+
return 'audio/mpeg';
|
|
5565
|
+
}
|
|
5566
|
+
}
|
|
5567
|
+
class TTSClient {
|
|
5568
|
+
constructor(provider, model) {
|
|
5569
|
+
this.provider = provider;
|
|
5570
|
+
this.model = model || 'default-tts-model';
|
|
5371
5571
|
}
|
|
5372
|
-
// ===== State Properties =====
|
|
5373
5572
|
/**
|
|
5374
|
-
*
|
|
5573
|
+
* Get the current model name
|
|
5375
5574
|
*/
|
|
5376
|
-
get
|
|
5377
|
-
return this.
|
|
5575
|
+
get modelName() {
|
|
5576
|
+
return this.model;
|
|
5378
5577
|
}
|
|
5379
|
-
// ===== Character Design & Memory System =====
|
|
5380
5578
|
/**
|
|
5381
|
-
*
|
|
5382
|
-
*
|
|
5579
|
+
* Synthesize text into speech audio
|
|
5580
|
+
* @param config - Full TTS configuration
|
|
5581
|
+
* @returns TTS result containing raw audio bytes and usage metadata
|
|
5383
5582
|
*/
|
|
5384
|
-
|
|
5385
|
-
this.
|
|
5583
|
+
async synthesize(config) {
|
|
5584
|
+
return this.provider.synthesize(Object.assign(Object.assign({}, config), { model: config.model || this.model }));
|
|
5386
5585
|
}
|
|
5387
5586
|
/**
|
|
5388
|
-
*
|
|
5587
|
+
* Synthesize text into speech and return it as a Blob (browser-friendly)
|
|
5588
|
+
* @param config - Full TTS configuration
|
|
5589
|
+
* @returns Audio Blob with the appropriate MIME type
|
|
5389
5590
|
*/
|
|
5390
|
-
|
|
5391
|
-
|
|
5591
|
+
async synthesizeToBlob(config) {
|
|
5592
|
+
const result = await this.synthesize(config);
|
|
5593
|
+
return new Blob([result.audio], { type: contentTypeFor(result.format) });
|
|
5392
5594
|
}
|
|
5393
5595
|
/**
|
|
5394
|
-
*
|
|
5395
|
-
*
|
|
5596
|
+
* Synthesize text into speech and return an object URL (browser only)
|
|
5597
|
+
* @param config - Full TTS configuration
|
|
5598
|
+
* @returns An object URL that can be assigned to an <audio> element
|
|
5599
|
+
* @throws PlayKitError if URL.createObjectURL is unavailable (e.g. Node.js)
|
|
5396
5600
|
*/
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5601
|
+
async synthesizeToObjectURL(config) {
|
|
5602
|
+
if (typeof URL === 'undefined' || typeof URL.createObjectURL !== 'function') {
|
|
5603
|
+
throw new PlayKitError('URL.createObjectURL is not available in this environment. ' +
|
|
5604
|
+
'Use synthesize() to access the raw audio bytes instead.', 'TTS_OBJECT_URL_UNAVAILABLE');
|
|
5605
|
+
}
|
|
5606
|
+
const blob = await this.synthesizeToBlob(config);
|
|
5607
|
+
return URL.createObjectURL(blob);
|
|
5608
|
+
}
|
|
5609
|
+
}
|
|
5610
|
+
|
|
5611
|
+
/**
|
|
5612
|
+
* Global AI Context Manager for managing NPC conversations and player context.
|
|
5613
|
+
*
|
|
5614
|
+
* Features:
|
|
5615
|
+
* - Player description management
|
|
5616
|
+
* - NPC conversation tracking
|
|
5617
|
+
* - Automatic conversation compaction (AutoCompact)
|
|
5618
|
+
*/
|
|
5619
|
+
/**
|
|
5620
|
+
* Global AI Context Manager
|
|
5621
|
+
* Manages NPC conversations and player context across the application
|
|
5622
|
+
*/
|
|
5623
|
+
class AIContextManager extends EventEmitter {
|
|
5624
|
+
constructor(config) {
|
|
5625
|
+
var _a, _b, _c, _d, _e;
|
|
5626
|
+
super();
|
|
5627
|
+
this.playerDescription = null;
|
|
5628
|
+
this.playerPrompt = null;
|
|
5629
|
+
this.playerMemories = new Map();
|
|
5630
|
+
this.npcStates = new Map();
|
|
5631
|
+
this.autoCompactTimer = null;
|
|
5632
|
+
this.chatClientFactory = null;
|
|
5633
|
+
this.logger = Logger.getLogger('AIContextManager');
|
|
5634
|
+
this.config = {
|
|
5635
|
+
enableAutoCompact: (_a = config === null || config === void 0 ? void 0 : config.enableAutoCompact) !== null && _a !== void 0 ? _a : false,
|
|
5636
|
+
autoCompactMinMessages: (_b = config === null || config === void 0 ? void 0 : config.autoCompactMinMessages) !== null && _b !== void 0 ? _b : 20,
|
|
5637
|
+
autoCompactTimeoutSeconds: (_c = config === null || config === void 0 ? void 0 : config.autoCompactTimeoutSeconds) !== null && _c !== void 0 ? _c : 300,
|
|
5638
|
+
autoCompactCheckInterval: (_d = config === null || config === void 0 ? void 0 : config.autoCompactCheckInterval) !== null && _d !== void 0 ? _d : 60000,
|
|
5639
|
+
fastModel: (_e = config === null || config === void 0 ? void 0 : config.fastModel) !== null && _e !== void 0 ? _e : '',
|
|
5640
|
+
};
|
|
5641
|
+
// Start auto-compact check if enabled
|
|
5642
|
+
if (this.config.enableAutoCompact) {
|
|
5643
|
+
this.startAutoCompactCheck();
|
|
5644
|
+
}
|
|
5400
5645
|
}
|
|
5646
|
+
// ===== Singleton Pattern =====
|
|
5401
5647
|
/**
|
|
5402
|
-
*
|
|
5403
|
-
*
|
|
5648
|
+
* Get the singleton instance of AIContextManager
|
|
5649
|
+
* Creates a new instance if one doesn't exist
|
|
5404
5650
|
*/
|
|
5405
|
-
|
|
5406
|
-
|
|
5651
|
+
static getInstance(config) {
|
|
5652
|
+
if (!AIContextManager._instance) {
|
|
5653
|
+
AIContextManager._instance = new AIContextManager(config);
|
|
5654
|
+
}
|
|
5655
|
+
return AIContextManager._instance;
|
|
5407
5656
|
}
|
|
5408
5657
|
/**
|
|
5409
|
-
*
|
|
5410
|
-
* Memories are appended to the character design to form the system prompt.
|
|
5411
|
-
* Set memoryContent to null or empty to remove the memory.
|
|
5412
|
-
* @param memoryName The name/key of the memory
|
|
5413
|
-
* @param memoryContent The content of the memory. Null or empty to remove.
|
|
5658
|
+
* Reset the singleton instance (useful for testing)
|
|
5414
5659
|
*/
|
|
5415
|
-
|
|
5416
|
-
if (
|
|
5417
|
-
|
|
5418
|
-
|
|
5660
|
+
static resetInstance() {
|
|
5661
|
+
if (AIContextManager._instance) {
|
|
5662
|
+
AIContextManager._instance.destroy();
|
|
5663
|
+
AIContextManager._instance = null;
|
|
5419
5664
|
}
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5665
|
+
}
|
|
5666
|
+
// ===== Configuration =====
|
|
5667
|
+
/**
|
|
5668
|
+
* Set the chat client factory for creating chat clients for summarization
|
|
5669
|
+
* Required for compaction to work
|
|
5670
|
+
*/
|
|
5671
|
+
setChatClientFactory(factory) {
|
|
5672
|
+
this.chatClientFactory = factory;
|
|
5673
|
+
}
|
|
5674
|
+
/**
|
|
5675
|
+
* Update configuration
|
|
5676
|
+
*/
|
|
5677
|
+
setConfig(config) {
|
|
5678
|
+
const wasAutoCompactEnabled = this.config.enableAutoCompact;
|
|
5679
|
+
this.config = Object.assign(Object.assign({}, this.config), config);
|
|
5680
|
+
// Handle auto-compact state change
|
|
5681
|
+
if (config.enableAutoCompact !== undefined) {
|
|
5682
|
+
if (config.enableAutoCompact && !wasAutoCompactEnabled) {
|
|
5683
|
+
this.startAutoCompactCheck();
|
|
5425
5684
|
}
|
|
5685
|
+
else if (!config.enableAutoCompact && wasAutoCompactEnabled) {
|
|
5686
|
+
this.stopAutoCompactCheck();
|
|
5687
|
+
}
|
|
5688
|
+
}
|
|
5689
|
+
}
|
|
5690
|
+
// ===== Player Description =====
|
|
5691
|
+
/**
|
|
5692
|
+
* Set the player's description for AI context.
|
|
5693
|
+
* Used when generating reply predictions and for NPC context.
|
|
5694
|
+
* @param description Description of the player character
|
|
5695
|
+
*/
|
|
5696
|
+
setPlayerDescription(description) {
|
|
5697
|
+
this.playerDescription = description;
|
|
5698
|
+
this.emit('playerDescriptionChanged', description);
|
|
5699
|
+
}
|
|
5700
|
+
/**
|
|
5701
|
+
* Get the current player description.
|
|
5702
|
+
* @returns The player description, or null if not set
|
|
5703
|
+
*/
|
|
5704
|
+
getPlayerDescription() {
|
|
5705
|
+
return this.playerDescription;
|
|
5706
|
+
}
|
|
5707
|
+
/**
|
|
5708
|
+
* Clear the player description.
|
|
5709
|
+
*/
|
|
5710
|
+
clearPlayerDescription() {
|
|
5711
|
+
this.playerDescription = null;
|
|
5712
|
+
this.emit('playerDescriptionChanged', null);
|
|
5713
|
+
}
|
|
5714
|
+
// ===== Player Prompt & Memory (for Reply Prediction) =====
|
|
5715
|
+
/**
|
|
5716
|
+
* Set the player's character prompt/persona.
|
|
5717
|
+
* This defines how the player character speaks and behaves.
|
|
5718
|
+
* Used when generating reply predictions to match the player's tone.
|
|
5719
|
+
* @param prompt The player character's persona/prompt
|
|
5720
|
+
*/
|
|
5721
|
+
setPlayerPrompt(prompt) {
|
|
5722
|
+
this.playerPrompt = prompt;
|
|
5723
|
+
}
|
|
5724
|
+
/**
|
|
5725
|
+
* Get the current player prompt.
|
|
5726
|
+
* @returns The player prompt, or null if not set
|
|
5727
|
+
*/
|
|
5728
|
+
getPlayerPrompt() {
|
|
5729
|
+
return this.playerPrompt;
|
|
5730
|
+
}
|
|
5731
|
+
/**
|
|
5732
|
+
* Set or update a memory for the player character.
|
|
5733
|
+
* Memories are appended to the player prompt to form the full player context.
|
|
5734
|
+
* Set memoryContent to null or empty to remove the memory.
|
|
5735
|
+
* @param memoryName The name/key of the memory
|
|
5736
|
+
* @param memoryContent The content of the memory. Null or empty to remove.
|
|
5737
|
+
*/
|
|
5738
|
+
setPlayerMemory(memoryName, memoryContent) {
|
|
5739
|
+
if (!memoryName) {
|
|
5740
|
+
this.logger.warn('Memory name cannot be empty');
|
|
5741
|
+
return;
|
|
5742
|
+
}
|
|
5743
|
+
if (!memoryContent) {
|
|
5744
|
+
// Remove memory if content is null or empty
|
|
5745
|
+
this.playerMemories.delete(memoryName);
|
|
5426
5746
|
}
|
|
5427
5747
|
else {
|
|
5428
5748
|
// Add or update memory
|
|
5429
|
-
this.
|
|
5430
|
-
this.emit('memory_set', memoryName, memoryContent);
|
|
5749
|
+
this.playerMemories.set(memoryName, memoryContent);
|
|
5431
5750
|
}
|
|
5432
5751
|
}
|
|
5433
5752
|
/**
|
|
5434
|
-
* Get a specific memory by name.
|
|
5753
|
+
* Get a specific player memory by name.
|
|
5435
5754
|
* @param memoryName The name of the memory to retrieve
|
|
5436
5755
|
* @returns The memory content, or undefined if not found
|
|
5437
5756
|
*/
|
|
5438
|
-
|
|
5439
|
-
return this.
|
|
5757
|
+
getPlayerMemory(memoryName) {
|
|
5758
|
+
return this.playerMemories.get(memoryName);
|
|
5440
5759
|
}
|
|
5441
5760
|
/**
|
|
5442
|
-
* Get all memory names currently stored.
|
|
5761
|
+
* Get all player memory names currently stored.
|
|
5443
5762
|
* @returns Array of memory names
|
|
5444
5763
|
*/
|
|
5445
|
-
|
|
5446
|
-
return Array.from(this.
|
|
5764
|
+
getPlayerMemoryNames() {
|
|
5765
|
+
return Array.from(this.playerMemories.keys());
|
|
5447
5766
|
}
|
|
5448
5767
|
/**
|
|
5449
|
-
* Clear all memories (but keep
|
|
5768
|
+
* Clear all player memories (but keep player prompt).
|
|
5450
5769
|
*/
|
|
5451
|
-
|
|
5452
|
-
this.
|
|
5453
|
-
this.emit('memories_cleared');
|
|
5770
|
+
clearPlayerMemories() {
|
|
5771
|
+
this.playerMemories.clear();
|
|
5454
5772
|
}
|
|
5455
5773
|
/**
|
|
5456
|
-
* Build the complete
|
|
5774
|
+
* Build the complete player context from PlayerPrompt + PlayerMemories.
|
|
5775
|
+
* Used by NPCClient for generating reply predictions.
|
|
5776
|
+
* @returns The combined player context string, or null if no context is set
|
|
5457
5777
|
*/
|
|
5458
|
-
|
|
5778
|
+
buildPlayerContext() {
|
|
5459
5779
|
const parts = [];
|
|
5460
|
-
if (this.
|
|
5461
|
-
parts.push(this.
|
|
5780
|
+
if (this.playerPrompt) {
|
|
5781
|
+
parts.push(this.playerPrompt);
|
|
5462
5782
|
}
|
|
5463
|
-
if (this.
|
|
5464
|
-
const memoryStrings = Array.from(this.
|
|
5783
|
+
if (this.playerMemories.size > 0) {
|
|
5784
|
+
const memoryStrings = Array.from(this.playerMemories.entries())
|
|
5465
5785
|
.map(([name, content]) => `[${name}]: ${content}`);
|
|
5466
|
-
parts.push('Memories:\n' + memoryStrings.join('\n'));
|
|
5786
|
+
parts.push('Player Memories:\n' + memoryStrings.join('\n'));
|
|
5787
|
+
}
|
|
5788
|
+
if (parts.length === 0) {
|
|
5789
|
+
return null;
|
|
5467
5790
|
}
|
|
5468
5791
|
return parts.join('\n\n');
|
|
5469
5792
|
}
|
|
5470
|
-
// =====
|
|
5471
|
-
/**
|
|
5472
|
-
* Enable or disable automatic reply prediction
|
|
5473
|
-
*/
|
|
5474
|
-
setGenerateReplyPrediction(enabled) {
|
|
5475
|
-
this.generateReplyPrediction = enabled;
|
|
5476
|
-
}
|
|
5793
|
+
// ===== NPC Tracking =====
|
|
5477
5794
|
/**
|
|
5478
|
-
*
|
|
5795
|
+
* Register an NPC for context management.
|
|
5796
|
+
* @param npc The NPC client to register
|
|
5479
5797
|
*/
|
|
5480
|
-
|
|
5481
|
-
|
|
5798
|
+
registerNpc(npc) {
|
|
5799
|
+
if (!npc)
|
|
5800
|
+
return;
|
|
5801
|
+
if (!this.npcStates.has(npc)) {
|
|
5802
|
+
this.npcStates.set(npc, {
|
|
5803
|
+
lastConversationTime: new Date(),
|
|
5804
|
+
isCompacted: false,
|
|
5805
|
+
compactionCount: 0,
|
|
5806
|
+
});
|
|
5807
|
+
}
|
|
5482
5808
|
}
|
|
5483
5809
|
/**
|
|
5484
|
-
*
|
|
5485
|
-
*
|
|
5486
|
-
* @param count Number of predictions to generate (default: uses predictionCount property)
|
|
5487
|
-
* @returns Array of predicted player replies, or empty array on failure
|
|
5810
|
+
* Unregister an NPC (call when NPC is destroyed/removed).
|
|
5811
|
+
* @param npc The NPC client to unregister
|
|
5488
5812
|
*/
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
this.logger.info('Not enough conversation history to generate predictions');
|
|
5494
|
-
return [];
|
|
5495
|
-
}
|
|
5496
|
-
try {
|
|
5497
|
-
// Get last NPC message
|
|
5498
|
-
const lastNpcMessage = (_a = [...this.history]
|
|
5499
|
-
.reverse()
|
|
5500
|
-
.find(m => m.role === 'assistant')) === null || _a === void 0 ? void 0 : _a.content;
|
|
5501
|
-
if (!lastNpcMessage) {
|
|
5502
|
-
this.logger.info('No NPC message found to generate predictions from');
|
|
5503
|
-
return [];
|
|
5504
|
-
}
|
|
5505
|
-
// Build recent history (last 6 non-system messages)
|
|
5506
|
-
const recentHistory = this.history
|
|
5507
|
-
.filter(m => m.role !== 'system')
|
|
5508
|
-
.slice(-6)
|
|
5509
|
-
.map(m => `${m.role}: ${m.content}`);
|
|
5510
|
-
// Build prompt for prediction generation
|
|
5511
|
-
const prompt = `Based on the conversation history below, generate exactly ${predictionNum} natural and contextually appropriate responses that the player might say next.
|
|
5512
|
-
|
|
5513
|
-
Context:
|
|
5514
|
-
- This is a conversation between a player and an NPC in a game
|
|
5515
|
-
- The NPC just said: "${lastNpcMessage}"
|
|
5516
|
-
|
|
5517
|
-
Conversation history:
|
|
5518
|
-
${recentHistory.join('\n')}
|
|
5519
|
-
|
|
5520
|
-
Requirements:
|
|
5521
|
-
1. Each response should be 1-2 sentences maximum
|
|
5522
|
-
2. Responses should be diverse in tone and intent
|
|
5523
|
-
3. Include a mix of questions, statements, and action-oriented responses
|
|
5524
|
-
4. Responses should feel natural for a player character
|
|
5525
|
-
|
|
5526
|
-
Output ONLY a JSON array of ${predictionNum} strings, nothing else:
|
|
5527
|
-
["response1", "response2", "response3", "response4"]`;
|
|
5528
|
-
const result = await this.chatClient.textGeneration({
|
|
5529
|
-
messages: [{ role: 'user', content: prompt }],
|
|
5530
|
-
temperature: 0.8,
|
|
5531
|
-
model: this.fastModel,
|
|
5532
|
-
});
|
|
5533
|
-
if (!result.content) {
|
|
5534
|
-
this.logger.warn('Failed to generate predictions: empty response');
|
|
5535
|
-
return [];
|
|
5536
|
-
}
|
|
5537
|
-
// Parse JSON response
|
|
5538
|
-
const predictions = this.parsePredictionsFromJson(result.content, predictionNum);
|
|
5539
|
-
if (predictions.length > 0) {
|
|
5540
|
-
this.emit('replyPredictions', predictions);
|
|
5541
|
-
}
|
|
5542
|
-
return predictions;
|
|
5543
|
-
}
|
|
5544
|
-
catch (error) {
|
|
5545
|
-
this.logger.error('Error generating predictions:', error);
|
|
5546
|
-
return [];
|
|
5547
|
-
}
|
|
5813
|
+
unregisterNpc(npc) {
|
|
5814
|
+
if (!npc)
|
|
5815
|
+
return;
|
|
5816
|
+
this.npcStates.delete(npc);
|
|
5548
5817
|
}
|
|
5549
5818
|
/**
|
|
5550
|
-
*
|
|
5819
|
+
* Record that a conversation occurred with an NPC.
|
|
5820
|
+
* Called after each Talk() exchange.
|
|
5821
|
+
* @param npc The NPC client that had a conversation
|
|
5551
5822
|
*/
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) {
|
|
5558
|
-
this.logger.warn('Could not find JSON array in prediction response');
|
|
5559
|
-
return this.extractPredictionsFromText(response, expectedCount);
|
|
5560
|
-
}
|
|
5561
|
-
const jsonArray = response.substring(startIndex, endIndex + 1);
|
|
5562
|
-
const parsed = JSON.parse(jsonArray);
|
|
5563
|
-
if (Array.isArray(parsed)) {
|
|
5564
|
-
return parsed
|
|
5565
|
-
.filter(item => typeof item === 'string' && item.trim())
|
|
5566
|
-
.slice(0, expectedCount);
|
|
5567
|
-
}
|
|
5568
|
-
return [];
|
|
5569
|
-
}
|
|
5570
|
-
catch (error) {
|
|
5571
|
-
this.logger.warn('Failed to parse predictions JSON:', error);
|
|
5572
|
-
return this.extractPredictionsFromText(response, expectedCount);
|
|
5823
|
+
recordConversation(npc) {
|
|
5824
|
+
if (!npc)
|
|
5825
|
+
return;
|
|
5826
|
+
if (!this.npcStates.has(npc)) {
|
|
5827
|
+
this.registerNpc(npc);
|
|
5573
5828
|
}
|
|
5829
|
+
const state = this.npcStates.get(npc);
|
|
5830
|
+
state.lastConversationTime = new Date();
|
|
5831
|
+
state.isCompacted = false; // Reset compaction flag on new conversation
|
|
5574
5832
|
}
|
|
5575
5833
|
/**
|
|
5576
|
-
*
|
|
5834
|
+
* Get all registered NPCs
|
|
5577
5835
|
*/
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
const lines = response.split(/[\n\r]+/).filter(line => line.trim());
|
|
5581
|
-
for (const line of lines) {
|
|
5582
|
-
let cleaned = line.trim();
|
|
5583
|
-
// Skip empty lines and JSON brackets
|
|
5584
|
-
if (!cleaned || cleaned === '[' || cleaned === ']')
|
|
5585
|
-
continue;
|
|
5586
|
-
// Remove common prefixes like "1.", "- ", etc.
|
|
5587
|
-
if (/^\d+\./.test(cleaned)) {
|
|
5588
|
-
cleaned = cleaned.replace(/^\d+\.\s*/, '');
|
|
5589
|
-
}
|
|
5590
|
-
else if (cleaned.startsWith('- ')) {
|
|
5591
|
-
cleaned = cleaned.substring(2);
|
|
5592
|
-
}
|
|
5593
|
-
// Remove surrounding quotes
|
|
5594
|
-
if (cleaned.startsWith('"') && cleaned.endsWith('"')) {
|
|
5595
|
-
cleaned = cleaned.slice(1, -1);
|
|
5596
|
-
}
|
|
5597
|
-
// Remove trailing comma
|
|
5598
|
-
if (cleaned.endsWith(',')) {
|
|
5599
|
-
cleaned = cleaned.slice(0, -1).trim();
|
|
5600
|
-
}
|
|
5601
|
-
if (cleaned && predictions.length < expectedCount) {
|
|
5602
|
-
predictions.push(cleaned);
|
|
5603
|
-
}
|
|
5604
|
-
}
|
|
5605
|
-
return predictions;
|
|
5836
|
+
getRegisteredNpcs() {
|
|
5837
|
+
return Array.from(this.npcStates.keys());
|
|
5606
5838
|
}
|
|
5607
5839
|
/**
|
|
5608
|
-
*
|
|
5840
|
+
* Get the conversation state for an NPC
|
|
5609
5841
|
*/
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
return;
|
|
5613
|
-
// Fire and forget - don't block the main response
|
|
5614
|
-
this.generateReplyPredictions().catch(err => {
|
|
5615
|
-
this.logger.error('Background prediction generation failed:', err);
|
|
5616
|
-
});
|
|
5842
|
+
getNpcState(npc) {
|
|
5843
|
+
return this.npcStates.get(npc);
|
|
5617
5844
|
}
|
|
5618
|
-
// =====
|
|
5845
|
+
// ===== Auto Compaction =====
|
|
5619
5846
|
/**
|
|
5620
|
-
*
|
|
5847
|
+
* Check if an NPC is eligible for compaction.
|
|
5848
|
+
* @param npc The NPC to check
|
|
5849
|
+
* @returns True if eligible for compaction
|
|
5621
5850
|
*/
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
// Trim history if needed
|
|
5642
|
-
this.trimHistory();
|
|
5643
|
-
this.emit('response', result.content);
|
|
5644
|
-
// Trigger reply prediction generation (fire and forget)
|
|
5645
|
-
this.triggerReplyPrediction();
|
|
5646
|
-
return result.content;
|
|
5647
|
-
}
|
|
5648
|
-
finally {
|
|
5649
|
-
this._isTalking = false;
|
|
5650
|
-
}
|
|
5851
|
+
isEligibleForCompaction(npc) {
|
|
5852
|
+
if (!npc)
|
|
5853
|
+
return false;
|
|
5854
|
+
const state = this.npcStates.get(npc);
|
|
5855
|
+
if (!state)
|
|
5856
|
+
return false;
|
|
5857
|
+
// Check if already compacted since last conversation
|
|
5858
|
+
if (state.isCompacted)
|
|
5859
|
+
return false;
|
|
5860
|
+
// Check message count
|
|
5861
|
+
const history = npc.getHistory();
|
|
5862
|
+
const nonSystemMessages = history.filter(m => m.role !== 'system').length;
|
|
5863
|
+
if (nonSystemMessages < this.config.autoCompactMinMessages)
|
|
5864
|
+
return false;
|
|
5865
|
+
// Check time since last conversation
|
|
5866
|
+
const timeSinceLastConversation = (Date.now() - state.lastConversationTime.getTime()) / 1000;
|
|
5867
|
+
if (timeSinceLastConversation < this.config.autoCompactTimeoutSeconds)
|
|
5868
|
+
return false;
|
|
5869
|
+
return true;
|
|
5651
5870
|
}
|
|
5652
5871
|
/**
|
|
5653
|
-
*
|
|
5872
|
+
* Manually trigger conversation compaction for a specific NPC.
|
|
5873
|
+
* Summarizes the conversation history and stores it as a memory.
|
|
5874
|
+
* @param npc The NPC to compact
|
|
5875
|
+
* @returns True if compaction succeeded
|
|
5654
5876
|
*/
|
|
5655
|
-
async
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
const userMessage = { role: 'user', content: message };
|
|
5660
|
-
this.history.push(userMessage);
|
|
5661
|
-
// Build messages array with system prompt
|
|
5662
|
-
const messages = [
|
|
5663
|
-
{ role: 'system', content: this.buildSystemPrompt() },
|
|
5664
|
-
...this.history,
|
|
5665
|
-
];
|
|
5666
|
-
// Generate response
|
|
5667
|
-
await this.chatClient.textGenerationStream({
|
|
5668
|
-
messages,
|
|
5669
|
-
temperature: this.temperature,
|
|
5670
|
-
onChunk,
|
|
5671
|
-
onComplete: (fullText) => {
|
|
5672
|
-
this._isTalking = false;
|
|
5673
|
-
// Add assistant response to history
|
|
5674
|
-
const assistantMessage = { role: 'assistant', content: fullText };
|
|
5675
|
-
this.history.push(assistantMessage);
|
|
5676
|
-
// Trim history if needed
|
|
5677
|
-
this.trimHistory();
|
|
5678
|
-
this.emit('response', fullText);
|
|
5679
|
-
// Trigger reply prediction generation (fire and forget)
|
|
5680
|
-
this.triggerReplyPrediction();
|
|
5681
|
-
if (onComplete) {
|
|
5682
|
-
onComplete(fullText);
|
|
5683
|
-
}
|
|
5684
|
-
},
|
|
5685
|
-
});
|
|
5877
|
+
async compactConversation(npc) {
|
|
5878
|
+
if (!npc) {
|
|
5879
|
+
this.logger.warn('Cannot compact: NPC is null');
|
|
5880
|
+
return false;
|
|
5686
5881
|
}
|
|
5687
|
-
|
|
5688
|
-
this.
|
|
5689
|
-
|
|
5882
|
+
if (!this.chatClientFactory) {
|
|
5883
|
+
this.logger.error('Cannot compact: No chat client factory set. Call setChatClientFactory() first.');
|
|
5884
|
+
return false;
|
|
5885
|
+
}
|
|
5886
|
+
const history = npc.getHistory();
|
|
5887
|
+
const nonSystemMessages = history.filter(m => m.role !== 'system');
|
|
5888
|
+
if (nonSystemMessages.length < 2) {
|
|
5889
|
+
this.logger.info('Skipping compaction: not enough messages');
|
|
5890
|
+
return false;
|
|
5690
5891
|
}
|
|
5691
|
-
}
|
|
5692
|
-
/**
|
|
5693
|
-
* Talk with structured output
|
|
5694
|
-
* @deprecated Use talkWithActions instead for NPC decision-making with actions
|
|
5695
|
-
*/
|
|
5696
|
-
async talkStructured(message, schemaName) {
|
|
5697
|
-
this.logger.warn('talkStructured is deprecated. Use talkWithActions instead for NPC decision-making with actions.');
|
|
5698
|
-
// Add user message to history
|
|
5699
|
-
const userMessage = { role: 'user', content: message };
|
|
5700
|
-
this.history.push(userMessage);
|
|
5701
|
-
// Generate structured response
|
|
5702
|
-
const result = await this.chatClient.generateStructured({
|
|
5703
|
-
schemaName,
|
|
5704
|
-
prompt: message,
|
|
5705
|
-
messages: [{ role: 'system', content: this.buildSystemPrompt() }, ...this.history],
|
|
5706
|
-
temperature: this.temperature,
|
|
5707
|
-
});
|
|
5708
|
-
// Add a text representation to history
|
|
5709
|
-
const assistantMessage = {
|
|
5710
|
-
role: 'assistant',
|
|
5711
|
-
content: JSON.stringify(result),
|
|
5712
|
-
};
|
|
5713
|
-
this.history.push(assistantMessage);
|
|
5714
|
-
this.trimHistory();
|
|
5715
|
-
return result;
|
|
5716
|
-
}
|
|
5717
|
-
/**
|
|
5718
|
-
* Talk to the NPC with available actions (non-streaming)
|
|
5719
|
-
* @param message The message to send
|
|
5720
|
-
* @param actions List of actions the NPC can perform
|
|
5721
|
-
* @returns Response containing text and any action calls
|
|
5722
|
-
*/
|
|
5723
|
-
async talkWithActions(message, actions) {
|
|
5724
|
-
this._isTalking = true;
|
|
5725
5892
|
try {
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5893
|
+
this.logger.info(`Starting compaction (${nonSystemMessages.length} messages)`);
|
|
5894
|
+
// Build conversation text for summarization
|
|
5895
|
+
const conversationText = nonSystemMessages
|
|
5896
|
+
.map(m => `${m.role}: ${m.content}`)
|
|
5897
|
+
.join('\n');
|
|
5898
|
+
// Create summarization prompt
|
|
5899
|
+
const summaryPrompt = `Summarize the following conversation concisely. Focus on:
|
|
5900
|
+
1. Key topics discussed
|
|
5901
|
+
2. Important information exchanged
|
|
5902
|
+
3. Any decisions or commitments made
|
|
5903
|
+
4. The emotional tone
|
|
5904
|
+
|
|
5905
|
+
Keep the summary under 200 words. Write in third person.
|
|
5906
|
+
|
|
5907
|
+
Conversation:
|
|
5908
|
+
${conversationText}`;
|
|
5909
|
+
// Use chat client for summarization
|
|
5910
|
+
const chatClient = this.chatClientFactory();
|
|
5911
|
+
const result = await chatClient.textGeneration({
|
|
5912
|
+
messages: [{ role: 'user', content: summaryPrompt }],
|
|
5913
|
+
temperature: 0.5,
|
|
5914
|
+
model: this.config.fastModel || undefined,
|
|
5744
5915
|
});
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
};
|
|
5751
|
-
// Extract tool calls if any
|
|
5752
|
-
if (result.tool_calls) {
|
|
5753
|
-
response.actionCalls = result.tool_calls.map(tc => ({
|
|
5754
|
-
id: tc.id,
|
|
5755
|
-
actionName: tc.function.name,
|
|
5756
|
-
arguments: this.parseToolArguments(tc.function.arguments),
|
|
5757
|
-
}));
|
|
5758
|
-
response.hasActions = response.actionCalls.length > 0;
|
|
5916
|
+
if (!result.content) {
|
|
5917
|
+
const error = 'Empty response from summarization';
|
|
5918
|
+
this.logger.error(`Compaction failed: ${error}`);
|
|
5919
|
+
this.emit('compactionFailed', npc, error);
|
|
5920
|
+
return false;
|
|
5759
5921
|
}
|
|
5760
|
-
//
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
this.emit('response', response.text);
|
|
5769
|
-
if (response.hasActions) {
|
|
5770
|
-
this.emit('actions', response.actionCalls);
|
|
5922
|
+
// Clear history and add summary as memory
|
|
5923
|
+
npc.clearHistory();
|
|
5924
|
+
npc.setMemory('PreviousConversationSummary', result.content);
|
|
5925
|
+
// Update state
|
|
5926
|
+
const state = this.npcStates.get(npc);
|
|
5927
|
+
if (state) {
|
|
5928
|
+
state.isCompacted = true;
|
|
5929
|
+
state.compactionCount++;
|
|
5771
5930
|
}
|
|
5772
|
-
|
|
5773
|
-
this.
|
|
5774
|
-
return
|
|
5775
|
-
}
|
|
5776
|
-
finally {
|
|
5777
|
-
this._isTalking = false;
|
|
5778
|
-
}
|
|
5779
|
-
}
|
|
5780
|
-
/**
|
|
5781
|
-
* Talk to the NPC with actions (streaming)
|
|
5782
|
-
* Text streams first, action calls are returned in onComplete
|
|
5783
|
-
*/
|
|
5784
|
-
async talkWithActionsStream(message, actions, onChunk, onComplete) {
|
|
5785
|
-
this._isTalking = true;
|
|
5786
|
-
try {
|
|
5787
|
-
// Add user message to history
|
|
5788
|
-
const userMessage = { role: 'user', content: message };
|
|
5789
|
-
this.history.push(userMessage);
|
|
5790
|
-
// Convert NpcActions to ChatTools
|
|
5791
|
-
const tools = actions
|
|
5792
|
-
.filter(a => a && a.enabled !== false)
|
|
5793
|
-
.map(a => npcActionToTool(a));
|
|
5794
|
-
// Build messages array with system prompt
|
|
5795
|
-
const messages = [
|
|
5796
|
-
{ role: 'system', content: this.buildSystemPrompt() },
|
|
5797
|
-
...this.history,
|
|
5798
|
-
];
|
|
5799
|
-
// Generate response with tools (streaming)
|
|
5800
|
-
await this.chatClient.textGenerationWithToolsStream({
|
|
5801
|
-
messages,
|
|
5802
|
-
temperature: this.temperature,
|
|
5803
|
-
tools,
|
|
5804
|
-
tool_choice: 'auto',
|
|
5805
|
-
onChunk,
|
|
5806
|
-
onComplete: (result) => {
|
|
5807
|
-
this._isTalking = false;
|
|
5808
|
-
// Build response
|
|
5809
|
-
const response = {
|
|
5810
|
-
text: result.content || '',
|
|
5811
|
-
actionCalls: [],
|
|
5812
|
-
hasActions: false,
|
|
5813
|
-
};
|
|
5814
|
-
// Extract tool calls if any
|
|
5815
|
-
if (result.tool_calls) {
|
|
5816
|
-
response.actionCalls = result.tool_calls.map(tc => ({
|
|
5817
|
-
id: tc.id,
|
|
5818
|
-
actionName: tc.function.name,
|
|
5819
|
-
arguments: this.parseToolArguments(tc.function.arguments),
|
|
5820
|
-
}));
|
|
5821
|
-
response.hasActions = response.actionCalls.length > 0;
|
|
5822
|
-
}
|
|
5823
|
-
// Add assistant response to history
|
|
5824
|
-
const assistantMessage = {
|
|
5825
|
-
role: 'assistant',
|
|
5826
|
-
content: response.text,
|
|
5827
|
-
tool_calls: result.tool_calls,
|
|
5828
|
-
};
|
|
5829
|
-
this.history.push(assistantMessage);
|
|
5830
|
-
this.trimHistory();
|
|
5831
|
-
this.emit('response', response.text);
|
|
5832
|
-
if (response.hasActions) {
|
|
5833
|
-
this.emit('actions', response.actionCalls);
|
|
5834
|
-
}
|
|
5835
|
-
// Trigger reply prediction generation (fire and forget)
|
|
5836
|
-
this.triggerReplyPrediction();
|
|
5837
|
-
if (onComplete) {
|
|
5838
|
-
onComplete(response);
|
|
5839
|
-
}
|
|
5840
|
-
},
|
|
5841
|
-
});
|
|
5931
|
+
this.logger.info(`Compaction completed. Summary: ${result.content.substring(0, 100)}...`);
|
|
5932
|
+
this.emit('npcCompacted', npc);
|
|
5933
|
+
return true;
|
|
5842
5934
|
}
|
|
5843
5935
|
catch (error) {
|
|
5844
|
-
|
|
5845
|
-
|
|
5936
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5937
|
+
this.logger.error(`Compaction error: ${errorMessage}`);
|
|
5938
|
+
this.emit('compactionFailed', npc, errorMessage);
|
|
5939
|
+
return false;
|
|
5846
5940
|
}
|
|
5847
5941
|
}
|
|
5848
|
-
// ===== Action Results Reporting =====
|
|
5849
5942
|
/**
|
|
5850
|
-
*
|
|
5851
|
-
*
|
|
5943
|
+
* Compact all registered NPCs that meet the eligibility criteria.
|
|
5944
|
+
* @returns Number of NPCs successfully compacted
|
|
5852
5945
|
*/
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5946
|
+
async compactAllEligible() {
|
|
5947
|
+
const eligibleNpcs = Array.from(this.npcStates.keys()).filter(npc => this.isEligibleForCompaction(npc));
|
|
5948
|
+
if (eligibleNpcs.length === 0) {
|
|
5949
|
+
return 0;
|
|
5950
|
+
}
|
|
5951
|
+
this.logger.info(`Compacting ${eligibleNpcs.length} eligible NPCs`);
|
|
5952
|
+
let successCount = 0;
|
|
5953
|
+
for (const npc of eligibleNpcs) {
|
|
5954
|
+
const success = await this.compactConversation(npc);
|
|
5955
|
+
if (success)
|
|
5956
|
+
successCount++;
|
|
5860
5957
|
}
|
|
5958
|
+
return successCount;
|
|
5861
5959
|
}
|
|
5960
|
+
// ===== Auto Compact Timer =====
|
|
5862
5961
|
/**
|
|
5863
|
-
*
|
|
5962
|
+
* Start the auto-compact check timer
|
|
5864
5963
|
*/
|
|
5865
|
-
|
|
5866
|
-
this.
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5964
|
+
startAutoCompactCheck() {
|
|
5965
|
+
if (this.autoCompactTimer) {
|
|
5966
|
+
this.stopAutoCompactCheck();
|
|
5967
|
+
}
|
|
5968
|
+
this.autoCompactTimer = setInterval(() => {
|
|
5969
|
+
this.runAutoCompactCheck();
|
|
5970
|
+
}, this.config.autoCompactCheckInterval);
|
|
5871
5971
|
}
|
|
5872
5972
|
/**
|
|
5873
|
-
*
|
|
5973
|
+
* Stop the auto-compact check timer
|
|
5874
5974
|
*/
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5975
|
+
stopAutoCompactCheck() {
|
|
5976
|
+
if (this.autoCompactTimer) {
|
|
5977
|
+
clearInterval(this.autoCompactTimer);
|
|
5978
|
+
this.autoCompactTimer = null;
|
|
5878
5979
|
}
|
|
5879
|
-
|
|
5880
|
-
|
|
5980
|
+
}
|
|
5981
|
+
/**
|
|
5982
|
+
* Run a single auto-compact check
|
|
5983
|
+
*/
|
|
5984
|
+
async runAutoCompactCheck() {
|
|
5985
|
+
if (!this.config.enableAutoCompact)
|
|
5986
|
+
return;
|
|
5987
|
+
const eligibleNpcs = Array.from(this.npcStates.keys()).filter(npc => this.isEligibleForCompaction(npc));
|
|
5988
|
+
for (const npc of eligibleNpcs) {
|
|
5989
|
+
// Fire and forget - don't block
|
|
5990
|
+
this.compactConversation(npc).catch(err => {
|
|
5991
|
+
this.logger.error('Auto-compact error:', err);
|
|
5992
|
+
});
|
|
5881
5993
|
}
|
|
5882
5994
|
}
|
|
5883
|
-
// =====
|
|
5995
|
+
// ===== Lifecycle =====
|
|
5884
5996
|
/**
|
|
5885
|
-
*
|
|
5997
|
+
* Enable auto-compaction
|
|
5886
5998
|
*/
|
|
5887
|
-
|
|
5888
|
-
|
|
5999
|
+
enableAutoCompact() {
|
|
6000
|
+
this.config.enableAutoCompact = true;
|
|
6001
|
+
this.startAutoCompactCheck();
|
|
5889
6002
|
}
|
|
5890
6003
|
/**
|
|
5891
|
-
*
|
|
6004
|
+
* Disable auto-compaction
|
|
5892
6005
|
*/
|
|
5893
|
-
|
|
5894
|
-
|
|
6006
|
+
disableAutoCompact() {
|
|
6007
|
+
this.config.enableAutoCompact = false;
|
|
6008
|
+
this.stopAutoCompactCheck();
|
|
5895
6009
|
}
|
|
5896
6010
|
/**
|
|
5897
|
-
*
|
|
5898
|
-
* The character design and memories will be preserved.
|
|
6011
|
+
* Clean up resources
|
|
5899
6012
|
*/
|
|
5900
|
-
|
|
6013
|
+
destroy() {
|
|
6014
|
+
this.stopAutoCompactCheck();
|
|
6015
|
+
this.npcStates.clear();
|
|
6016
|
+
this.playerDescription = null;
|
|
6017
|
+
this.playerPrompt = null;
|
|
6018
|
+
this.playerMemories.clear();
|
|
6019
|
+
this.removeAllListeners();
|
|
6020
|
+
}
|
|
6021
|
+
}
|
|
6022
|
+
AIContextManager._instance = null;
|
|
6023
|
+
/**
|
|
6024
|
+
* Default AIContextManager instance
|
|
6025
|
+
* Can be used as a global context manager
|
|
6026
|
+
*/
|
|
6027
|
+
const defaultContextManager = AIContextManager.getInstance();
|
|
6028
|
+
|
|
6029
|
+
/**
|
|
6030
|
+
* NPC Client for simplified conversation management
|
|
6031
|
+
* Automatically handles conversation history
|
|
6032
|
+
*
|
|
6033
|
+
* Key Features:
|
|
6034
|
+
* - Call talk() for all interactions - actions are handled automatically
|
|
6035
|
+
* - Memory system for persistent NPC context
|
|
6036
|
+
* - Reply prediction for suggesting player responses
|
|
6037
|
+
* - Automatic conversation history management
|
|
6038
|
+
*/
|
|
6039
|
+
class NPCClient extends EventEmitter {
|
|
6040
|
+
constructor(chatClient, config) {
|
|
6041
|
+
var _a, _b, _c;
|
|
6042
|
+
super();
|
|
6043
|
+
this._isTalking = false;
|
|
6044
|
+
this.logger = Logger.getLogger('NPCClient');
|
|
6045
|
+
this.chatClient = chatClient;
|
|
6046
|
+
// Support both characterDesign and legacy systemPrompt
|
|
6047
|
+
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.';
|
|
6048
|
+
this.temperature = (_a = config === null || config === void 0 ? void 0 : config.temperature) !== null && _a !== void 0 ? _a : 0.7;
|
|
6049
|
+
this.maxHistoryLength = (config === null || config === void 0 ? void 0 : config.maxHistoryLength) || 50;
|
|
6050
|
+
this.generateReplyPrediction = (_b = config === null || config === void 0 ? void 0 : config.generateReplyPrediction) !== null && _b !== void 0 ? _b : false;
|
|
6051
|
+
this.predictionCount = Math.max(2, Math.min(6, (_c = config === null || config === void 0 ? void 0 : config.predictionCount) !== null && _c !== void 0 ? _c : 4));
|
|
6052
|
+
this.fastModel = config === null || config === void 0 ? void 0 : config.fastModel;
|
|
5901
6053
|
this.history = [];
|
|
5902
|
-
this.
|
|
6054
|
+
this.memories = new Map();
|
|
5903
6055
|
}
|
|
6056
|
+
// ===== State Properties =====
|
|
5904
6057
|
/**
|
|
5905
|
-
*
|
|
5906
|
-
* @returns true if reverted, false if not enough history
|
|
6058
|
+
* Whether the NPC is currently processing a request
|
|
5907
6059
|
*/
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
6060
|
+
get isTalking() {
|
|
6061
|
+
return this._isTalking;
|
|
6062
|
+
}
|
|
6063
|
+
// ===== Character Design & Memory System =====
|
|
6064
|
+
/**
|
|
6065
|
+
* Set the character design for the NPC.
|
|
6066
|
+
* The system prompt is composed of CharacterDesign + all Memories.
|
|
6067
|
+
*/
|
|
6068
|
+
setCharacterDesign(design) {
|
|
6069
|
+
this.characterDesign = design;
|
|
6070
|
+
}
|
|
6071
|
+
/**
|
|
6072
|
+
* Get the current character design
|
|
6073
|
+
*/
|
|
6074
|
+
getCharacterDesign() {
|
|
6075
|
+
return this.characterDesign;
|
|
6076
|
+
}
|
|
6077
|
+
/**
|
|
6078
|
+
* @deprecated Use setCharacterDesign instead.
|
|
6079
|
+
* This method is kept for backwards compatibility.
|
|
6080
|
+
*/
|
|
6081
|
+
setSystemPrompt(prompt) {
|
|
6082
|
+
this.logger.warn('setSystemPrompt is deprecated. Use setCharacterDesign instead.');
|
|
6083
|
+
this.setCharacterDesign(prompt);
|
|
6084
|
+
}
|
|
6085
|
+
/**
|
|
6086
|
+
* @deprecated Use getCharacterDesign instead.
|
|
6087
|
+
* This method is kept for backwards compatibility.
|
|
6088
|
+
*/
|
|
6089
|
+
getSystemPrompt() {
|
|
6090
|
+
return this.buildSystemPrompt();
|
|
6091
|
+
}
|
|
6092
|
+
/**
|
|
6093
|
+
* Set or update a memory for the NPC.
|
|
6094
|
+
* Memories are appended to the character design to form the system prompt.
|
|
6095
|
+
* Set memoryContent to null or empty to remove the memory.
|
|
6096
|
+
* @param memoryName The name/key of the memory
|
|
6097
|
+
* @param memoryContent The content of the memory. Null or empty to remove.
|
|
6098
|
+
*/
|
|
6099
|
+
setMemory(memoryName, memoryContent) {
|
|
6100
|
+
if (!memoryName) {
|
|
6101
|
+
this.logger.warn('Memory name cannot be empty');
|
|
6102
|
+
return;
|
|
6103
|
+
}
|
|
6104
|
+
if (!memoryContent) {
|
|
6105
|
+
// Remove memory if content is null or empty
|
|
6106
|
+
if (this.memories.has(memoryName)) {
|
|
6107
|
+
this.memories.delete(memoryName);
|
|
6108
|
+
this.emit('memory_removed', memoryName);
|
|
5918
6109
|
}
|
|
5919
6110
|
}
|
|
5920
|
-
|
|
5921
|
-
//
|
|
5922
|
-
this.
|
|
5923
|
-
this.
|
|
5924
|
-
this.emit('history_reverted');
|
|
5925
|
-
return true;
|
|
6111
|
+
else {
|
|
6112
|
+
// Add or update memory
|
|
6113
|
+
this.memories.set(memoryName, memoryContent);
|
|
6114
|
+
this.emit('memory_set', memoryName, memoryContent);
|
|
5926
6115
|
}
|
|
5927
|
-
return false;
|
|
5928
6116
|
}
|
|
5929
6117
|
/**
|
|
5930
|
-
*
|
|
5931
|
-
* @param
|
|
5932
|
-
* @returns
|
|
6118
|
+
* Get a specific memory by name.
|
|
6119
|
+
* @param memoryName The name of the memory to retrieve
|
|
6120
|
+
* @returns The memory content, or undefined if not found
|
|
5933
6121
|
*/
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
6122
|
+
getMemory(memoryName) {
|
|
6123
|
+
return this.memories.get(memoryName);
|
|
6124
|
+
}
|
|
6125
|
+
/**
|
|
6126
|
+
* Get all memory names currently stored.
|
|
6127
|
+
* @returns Array of memory names
|
|
6128
|
+
*/
|
|
6129
|
+
getMemoryNames() {
|
|
6130
|
+
return Array.from(this.memories.keys());
|
|
6131
|
+
}
|
|
6132
|
+
/**
|
|
6133
|
+
* Clear all memories (but keep character design).
|
|
6134
|
+
*/
|
|
6135
|
+
clearMemories() {
|
|
6136
|
+
this.memories.clear();
|
|
6137
|
+
this.emit('memories_cleared');
|
|
6138
|
+
}
|
|
6139
|
+
/**
|
|
6140
|
+
* Build the complete system prompt from CharacterDesign + Memories.
|
|
6141
|
+
*/
|
|
6142
|
+
buildSystemPrompt() {
|
|
6143
|
+
const parts = [];
|
|
6144
|
+
if (this.characterDesign) {
|
|
6145
|
+
parts.push(this.characterDesign);
|
|
5943
6146
|
}
|
|
5944
|
-
|
|
6147
|
+
if (this.memories.size > 0) {
|
|
6148
|
+
const memoryStrings = Array.from(this.memories.entries())
|
|
6149
|
+
.map(([name, content]) => `[${name}]: ${content}`);
|
|
6150
|
+
parts.push('Memories:\n' + memoryStrings.join('\n'));
|
|
6151
|
+
}
|
|
6152
|
+
return parts.join('\n\n');
|
|
6153
|
+
}
|
|
6154
|
+
// ===== Reply Prediction =====
|
|
6155
|
+
/**
|
|
6156
|
+
* Enable or disable automatic reply prediction
|
|
6157
|
+
*/
|
|
6158
|
+
setGenerateReplyPrediction(enabled) {
|
|
6159
|
+
this.generateReplyPrediction = enabled;
|
|
5945
6160
|
}
|
|
5946
6161
|
/**
|
|
5947
|
-
*
|
|
5948
|
-
* @deprecated Use revertHistory() or revertChatMessages() instead
|
|
6162
|
+
* Set the number of predictions to generate
|
|
5949
6163
|
*/
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
this.history = this.history.slice(0, index + 1);
|
|
5953
|
-
this.emit('history_reverted', index);
|
|
5954
|
-
}
|
|
6164
|
+
setPredictionCount(count) {
|
|
6165
|
+
this.predictionCount = Math.max(2, Math.min(6, count));
|
|
5955
6166
|
}
|
|
5956
6167
|
/**
|
|
5957
|
-
*
|
|
6168
|
+
* Manually generate reply predictions based on current conversation.
|
|
6169
|
+
* Uses the fast model for quick generation.
|
|
6170
|
+
* @param tempPrompt Optional temporary prompt to influence the prediction style/tone
|
|
6171
|
+
* @param count Number of predictions to generate (default: uses predictionCount property)
|
|
6172
|
+
* @returns Array of predicted player replies, or empty array on failure
|
|
5958
6173
|
*/
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
this.
|
|
6174
|
+
async generateReplyPredictions(tempPrompt, count) {
|
|
6175
|
+
var _a;
|
|
6176
|
+
const predictionNum = count !== null && count !== void 0 ? count : this.predictionCount;
|
|
6177
|
+
if (this.history.length < 2) {
|
|
6178
|
+
this.logger.info('Not enough conversation history to generate predictions');
|
|
6179
|
+
return [];
|
|
6180
|
+
}
|
|
6181
|
+
try {
|
|
6182
|
+
// Get last NPC message
|
|
6183
|
+
const lastNpcMessage = (_a = [...this.history]
|
|
6184
|
+
.reverse()
|
|
6185
|
+
.find(m => m.role === 'assistant')) === null || _a === void 0 ? void 0 : _a.content;
|
|
6186
|
+
if (!lastNpcMessage) {
|
|
6187
|
+
this.logger.info('No NPC message found to generate predictions from');
|
|
6188
|
+
return [];
|
|
6189
|
+
}
|
|
6190
|
+
// Build recent history (last 6 non-system messages)
|
|
6191
|
+
const recentHistory = this.history
|
|
6192
|
+
.filter(m => m.role !== 'system')
|
|
6193
|
+
.slice(-6)
|
|
6194
|
+
.map(m => `${m.role}: ${m.content}`);
|
|
6195
|
+
// Get player context from AIContextManager
|
|
6196
|
+
const contextManager = AIContextManager.getInstance();
|
|
6197
|
+
const playerContext = contextManager.buildPlayerContext();
|
|
6198
|
+
// Build player character section
|
|
6199
|
+
let playerCharacterSection = '';
|
|
6200
|
+
if (playerContext || tempPrompt) {
|
|
6201
|
+
playerCharacterSection = '\nPlayer Character:\n';
|
|
6202
|
+
if (playerContext) {
|
|
6203
|
+
playerCharacterSection += playerContext + '\n';
|
|
6204
|
+
}
|
|
6205
|
+
if (tempPrompt) {
|
|
6206
|
+
playerCharacterSection += `Additional guidance: ${tempPrompt}\n`;
|
|
6207
|
+
}
|
|
6208
|
+
}
|
|
6209
|
+
// Build prompt for prediction generation
|
|
6210
|
+
const prompt = `Based on the conversation history below, generate exactly ${predictionNum} natural and contextually appropriate responses that the player might say next.
|
|
6211
|
+
|
|
6212
|
+
Context:
|
|
6213
|
+
- This is a conversation between a player and an NPC in a game
|
|
6214
|
+
- The NPC just said: "${lastNpcMessage}"
|
|
6215
|
+
${playerCharacterSection}
|
|
6216
|
+
Conversation history:
|
|
6217
|
+
${recentHistory.join('\n')}
|
|
6218
|
+
|
|
6219
|
+
Requirements:
|
|
6220
|
+
1. Each response should be 1-2 sentences maximum
|
|
6221
|
+
2. Responses should be diverse in tone and intent
|
|
6222
|
+
3. Include a mix of questions, statements, and action-oriented responses
|
|
6223
|
+
4. Responses should feel natural for the player character${playerContext || tempPrompt ? ' and match their personality/tone' : ''}
|
|
6224
|
+
|
|
6225
|
+
Output ONLY a JSON array of ${predictionNum} strings, nothing else:
|
|
6226
|
+
["response1", "response2", "response3", "response4"]`;
|
|
6227
|
+
const result = await this.chatClient.textGeneration({
|
|
6228
|
+
messages: [{ role: 'user', content: prompt }],
|
|
6229
|
+
temperature: 0.8,
|
|
6230
|
+
model: this.fastModel,
|
|
6231
|
+
});
|
|
6232
|
+
if (!result.content) {
|
|
6233
|
+
this.logger.warn('Failed to generate predictions: empty response');
|
|
6234
|
+
return [];
|
|
6235
|
+
}
|
|
6236
|
+
// Parse JSON response
|
|
6237
|
+
const predictions = this.parsePredictionsFromJson(result.content, predictionNum);
|
|
6238
|
+
if (predictions.length > 0) {
|
|
6239
|
+
this.emit('replyPredictions', predictions);
|
|
6240
|
+
}
|
|
6241
|
+
return predictions;
|
|
6242
|
+
}
|
|
6243
|
+
catch (error) {
|
|
6244
|
+
this.logger.error('Error generating predictions:', error);
|
|
6245
|
+
return [];
|
|
6246
|
+
}
|
|
5962
6247
|
}
|
|
5963
6248
|
/**
|
|
5964
|
-
*
|
|
6249
|
+
* Parse predictions from JSON array response
|
|
5965
6250
|
*/
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
6251
|
+
parsePredictionsFromJson(response, expectedCount) {
|
|
6252
|
+
try {
|
|
6253
|
+
// Try to find JSON array in response
|
|
6254
|
+
const startIndex = response.indexOf('[');
|
|
6255
|
+
const endIndex = response.lastIndexOf(']');
|
|
6256
|
+
if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) {
|
|
6257
|
+
this.logger.warn('Could not find JSON array in prediction response');
|
|
6258
|
+
return this.extractPredictionsFromText(response, expectedCount);
|
|
6259
|
+
}
|
|
6260
|
+
const jsonArray = response.substring(startIndex, endIndex + 1);
|
|
6261
|
+
const parsed = JSON.parse(jsonArray);
|
|
6262
|
+
if (Array.isArray(parsed)) {
|
|
6263
|
+
return parsed
|
|
6264
|
+
.filter(item => typeof item === 'string' && item.trim())
|
|
6265
|
+
.slice(0, expectedCount);
|
|
6266
|
+
}
|
|
6267
|
+
return [];
|
|
6268
|
+
}
|
|
6269
|
+
catch (error) {
|
|
6270
|
+
this.logger.warn('Failed to parse predictions JSON:', error);
|
|
6271
|
+
return this.extractPredictionsFromText(response, expectedCount);
|
|
5970
6272
|
}
|
|
5971
|
-
this.appendMessage({ role: role, content });
|
|
5972
6273
|
}
|
|
5973
6274
|
/**
|
|
5974
|
-
*
|
|
6275
|
+
* Fallback: Extract predictions from text when JSON parsing fails
|
|
5975
6276
|
*/
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
6277
|
+
extractPredictionsFromText(response, expectedCount) {
|
|
6278
|
+
const predictions = [];
|
|
6279
|
+
const lines = response.split(/[\n\r]+/).filter(line => line.trim());
|
|
6280
|
+
for (const line of lines) {
|
|
6281
|
+
let cleaned = line.trim();
|
|
6282
|
+
// Skip empty lines and JSON brackets
|
|
6283
|
+
if (!cleaned || cleaned === '[' || cleaned === ']')
|
|
6284
|
+
continue;
|
|
6285
|
+
// Remove common prefixes like "1.", "- ", etc.
|
|
6286
|
+
if (/^\d+\./.test(cleaned)) {
|
|
6287
|
+
cleaned = cleaned.replace(/^\d+\.\s*/, '');
|
|
6288
|
+
}
|
|
6289
|
+
else if (cleaned.startsWith('- ')) {
|
|
6290
|
+
cleaned = cleaned.substring(2);
|
|
6291
|
+
}
|
|
6292
|
+
// Remove surrounding quotes
|
|
6293
|
+
if (cleaned.startsWith('"') && cleaned.endsWith('"')) {
|
|
6294
|
+
cleaned = cleaned.slice(1, -1);
|
|
6295
|
+
}
|
|
6296
|
+
// Remove trailing comma
|
|
6297
|
+
if (cleaned.endsWith(',')) {
|
|
6298
|
+
cleaned = cleaned.slice(0, -1).trim();
|
|
6299
|
+
}
|
|
6300
|
+
if (cleaned && predictions.length < expectedCount) {
|
|
6301
|
+
predictions.push(cleaned);
|
|
6302
|
+
}
|
|
5980
6303
|
}
|
|
6304
|
+
return predictions;
|
|
5981
6305
|
}
|
|
5982
|
-
// ===== Save/Load =====
|
|
5983
6306
|
/**
|
|
5984
|
-
*
|
|
5985
|
-
* Includes characterDesign, memories, and history.
|
|
6307
|
+
* Internal method to trigger prediction generation after NPC response
|
|
5986
6308
|
*/
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
6309
|
+
async triggerReplyPrediction() {
|
|
6310
|
+
if (!this.generateReplyPrediction)
|
|
6311
|
+
return;
|
|
6312
|
+
// Fire and forget - don't block the main response
|
|
6313
|
+
this.generateReplyPredictions().catch(err => {
|
|
6314
|
+
this.logger.error('Background prediction generation failed:', err);
|
|
6315
|
+
});
|
|
5994
6316
|
}
|
|
6317
|
+
// ===== Main API - Talk Methods =====
|
|
5995
6318
|
/**
|
|
5996
|
-
*
|
|
5997
|
-
* Restores characterDesign, memories, and history.
|
|
6319
|
+
* Talk to the NPC (non-streaming)
|
|
5998
6320
|
*/
|
|
5999
|
-
|
|
6321
|
+
async talk(message) {
|
|
6322
|
+
this._isTalking = true;
|
|
6000
6323
|
try {
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
this.
|
|
6004
|
-
//
|
|
6005
|
-
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
this.
|
|
6020
|
-
|
|
6324
|
+
// Add user message to history
|
|
6325
|
+
const userMessage = { role: 'user', content: message };
|
|
6326
|
+
this.history.push(userMessage);
|
|
6327
|
+
// Build messages array with system prompt
|
|
6328
|
+
const messages = [
|
|
6329
|
+
{ role: 'system', content: this.buildSystemPrompt() },
|
|
6330
|
+
...this.history,
|
|
6331
|
+
];
|
|
6332
|
+
// Generate response
|
|
6333
|
+
const result = await this.chatClient.textGeneration({
|
|
6334
|
+
messages,
|
|
6335
|
+
temperature: this.temperature,
|
|
6336
|
+
});
|
|
6337
|
+
// Add assistant response to history
|
|
6338
|
+
const assistantMessage = { role: 'assistant', content: result.content };
|
|
6339
|
+
this.history.push(assistantMessage);
|
|
6340
|
+
// Trim history if needed
|
|
6341
|
+
this.trimHistory();
|
|
6342
|
+
this.emit('response', result.content);
|
|
6343
|
+
// Trigger reply prediction generation (fire and forget)
|
|
6344
|
+
this.triggerReplyPrediction();
|
|
6345
|
+
return result.content;
|
|
6021
6346
|
}
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
/**
|
|
6026
|
-
* Global AI Context Manager for managing NPC conversations and player context.
|
|
6027
|
-
*
|
|
6028
|
-
* Features:
|
|
6029
|
-
* - Player description management
|
|
6030
|
-
* - NPC conversation tracking
|
|
6031
|
-
* - Automatic conversation compaction (AutoCompact)
|
|
6032
|
-
*/
|
|
6033
|
-
/**
|
|
6034
|
-
* Global AI Context Manager
|
|
6035
|
-
* Manages NPC conversations and player context across the application
|
|
6036
|
-
*/
|
|
6037
|
-
class AIContextManager extends EventEmitter {
|
|
6038
|
-
constructor(config) {
|
|
6039
|
-
var _a, _b, _c, _d, _e;
|
|
6040
|
-
super();
|
|
6041
|
-
this.playerDescription = null;
|
|
6042
|
-
this.npcStates = new Map();
|
|
6043
|
-
this.autoCompactTimer = null;
|
|
6044
|
-
this.chatClientFactory = null;
|
|
6045
|
-
this.logger = Logger.getLogger('AIContextManager');
|
|
6046
|
-
this.config = {
|
|
6047
|
-
enableAutoCompact: (_a = config === null || config === void 0 ? void 0 : config.enableAutoCompact) !== null && _a !== void 0 ? _a : false,
|
|
6048
|
-
autoCompactMinMessages: (_b = config === null || config === void 0 ? void 0 : config.autoCompactMinMessages) !== null && _b !== void 0 ? _b : 20,
|
|
6049
|
-
autoCompactTimeoutSeconds: (_c = config === null || config === void 0 ? void 0 : config.autoCompactTimeoutSeconds) !== null && _c !== void 0 ? _c : 300,
|
|
6050
|
-
autoCompactCheckInterval: (_d = config === null || config === void 0 ? void 0 : config.autoCompactCheckInterval) !== null && _d !== void 0 ? _d : 60000,
|
|
6051
|
-
fastModel: (_e = config === null || config === void 0 ? void 0 : config.fastModel) !== null && _e !== void 0 ? _e : '',
|
|
6052
|
-
};
|
|
6053
|
-
// Start auto-compact check if enabled
|
|
6054
|
-
if (this.config.enableAutoCompact) {
|
|
6055
|
-
this.startAutoCompactCheck();
|
|
6347
|
+
finally {
|
|
6348
|
+
this._isTalking = false;
|
|
6056
6349
|
}
|
|
6057
6350
|
}
|
|
6058
|
-
// ===== Singleton Pattern =====
|
|
6059
6351
|
/**
|
|
6060
|
-
*
|
|
6061
|
-
* Creates a new instance if one doesn't exist
|
|
6352
|
+
* Talk to the NPC with streaming
|
|
6062
6353
|
*/
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6354
|
+
async talkStream(message, onChunk, onComplete) {
|
|
6355
|
+
this._isTalking = true;
|
|
6356
|
+
try {
|
|
6357
|
+
// Add user message to history
|
|
6358
|
+
const userMessage = { role: 'user', content: message };
|
|
6359
|
+
this.history.push(userMessage);
|
|
6360
|
+
// Build messages array with system prompt
|
|
6361
|
+
const messages = [
|
|
6362
|
+
{ role: 'system', content: this.buildSystemPrompt() },
|
|
6363
|
+
...this.history,
|
|
6364
|
+
];
|
|
6365
|
+
// Generate response
|
|
6366
|
+
await this.chatClient.textGenerationStream({
|
|
6367
|
+
messages,
|
|
6368
|
+
temperature: this.temperature,
|
|
6369
|
+
onChunk,
|
|
6370
|
+
onComplete: (fullText) => {
|
|
6371
|
+
this._isTalking = false;
|
|
6372
|
+
// Add assistant response to history
|
|
6373
|
+
const assistantMessage = { role: 'assistant', content: fullText };
|
|
6374
|
+
this.history.push(assistantMessage);
|
|
6375
|
+
// Trim history if needed
|
|
6376
|
+
this.trimHistory();
|
|
6377
|
+
this.emit('response', fullText);
|
|
6378
|
+
// Trigger reply prediction generation (fire and forget)
|
|
6379
|
+
this.triggerReplyPrediction();
|
|
6380
|
+
if (onComplete) {
|
|
6381
|
+
onComplete(fullText);
|
|
6382
|
+
}
|
|
6383
|
+
},
|
|
6384
|
+
});
|
|
6066
6385
|
}
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
* Reset the singleton instance (useful for testing)
|
|
6071
|
-
*/
|
|
6072
|
-
static resetInstance() {
|
|
6073
|
-
if (AIContextManager._instance) {
|
|
6074
|
-
AIContextManager._instance.destroy();
|
|
6075
|
-
AIContextManager._instance = null;
|
|
6386
|
+
catch (error) {
|
|
6387
|
+
this._isTalking = false;
|
|
6388
|
+
throw error;
|
|
6076
6389
|
}
|
|
6077
6390
|
}
|
|
6078
|
-
// ===== Configuration =====
|
|
6079
6391
|
/**
|
|
6080
|
-
*
|
|
6081
|
-
*
|
|
6392
|
+
* Talk with structured output
|
|
6393
|
+
* @deprecated Use talkWithActions instead for NPC decision-making with actions
|
|
6082
6394
|
*/
|
|
6083
|
-
|
|
6084
|
-
this.
|
|
6395
|
+
async talkStructured(message, schemaName) {
|
|
6396
|
+
this.logger.warn('talkStructured is deprecated. Use talkWithActions instead for NPC decision-making with actions.');
|
|
6397
|
+
// Add user message to history
|
|
6398
|
+
const userMessage = { role: 'user', content: message };
|
|
6399
|
+
this.history.push(userMessage);
|
|
6400
|
+
// Generate structured response
|
|
6401
|
+
const result = await this.chatClient.generateStructured({
|
|
6402
|
+
schemaName,
|
|
6403
|
+
prompt: message,
|
|
6404
|
+
messages: [{ role: 'system', content: this.buildSystemPrompt() }, ...this.history],
|
|
6405
|
+
temperature: this.temperature,
|
|
6406
|
+
});
|
|
6407
|
+
// Add a text representation to history
|
|
6408
|
+
const assistantMessage = {
|
|
6409
|
+
role: 'assistant',
|
|
6410
|
+
content: JSON.stringify(result),
|
|
6411
|
+
};
|
|
6412
|
+
this.history.push(assistantMessage);
|
|
6413
|
+
this.trimHistory();
|
|
6414
|
+
return result;
|
|
6085
6415
|
}
|
|
6086
6416
|
/**
|
|
6087
|
-
*
|
|
6417
|
+
* Talk to the NPC with available actions (non-streaming)
|
|
6418
|
+
* @param message The message to send
|
|
6419
|
+
* @param actions List of actions the NPC can perform
|
|
6420
|
+
* @returns Response containing text and any action calls
|
|
6088
6421
|
*/
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6422
|
+
async talkWithActions(message, actions) {
|
|
6423
|
+
this._isTalking = true;
|
|
6424
|
+
try {
|
|
6425
|
+
// Add user message to history
|
|
6426
|
+
const userMessage = { role: 'user', content: message };
|
|
6427
|
+
this.history.push(userMessage);
|
|
6428
|
+
// Convert NpcActions to ChatTools
|
|
6429
|
+
const tools = actions
|
|
6430
|
+
.filter(a => a && a.enabled !== false)
|
|
6431
|
+
.map(a => npcActionToTool(a));
|
|
6432
|
+
// Build messages array with system prompt
|
|
6433
|
+
const messages = [
|
|
6434
|
+
{ role: 'system', content: this.buildSystemPrompt() },
|
|
6435
|
+
...this.history,
|
|
6436
|
+
];
|
|
6437
|
+
// Generate response with tools
|
|
6438
|
+
const result = await this.chatClient.textGenerationWithTools({
|
|
6439
|
+
messages,
|
|
6440
|
+
temperature: this.temperature,
|
|
6441
|
+
tools,
|
|
6442
|
+
tool_choice: 'auto',
|
|
6443
|
+
});
|
|
6444
|
+
// Build response
|
|
6445
|
+
const response = {
|
|
6446
|
+
text: result.content || '',
|
|
6447
|
+
actionCalls: [],
|
|
6448
|
+
hasActions: false,
|
|
6449
|
+
};
|
|
6450
|
+
// Extract tool calls if any
|
|
6451
|
+
if (result.tool_calls) {
|
|
6452
|
+
response.actionCalls = result.tool_calls.map(tc => ({
|
|
6453
|
+
id: tc.id,
|
|
6454
|
+
actionName: tc.function.name,
|
|
6455
|
+
arguments: this.parseToolArguments(tc.function.arguments),
|
|
6456
|
+
}));
|
|
6457
|
+
response.hasActions = response.actionCalls.length > 0;
|
|
6096
6458
|
}
|
|
6097
|
-
|
|
6098
|
-
|
|
6459
|
+
// Add assistant response to history
|
|
6460
|
+
const assistantMessage = {
|
|
6461
|
+
role: 'assistant',
|
|
6462
|
+
content: response.text,
|
|
6463
|
+
tool_calls: result.tool_calls,
|
|
6464
|
+
};
|
|
6465
|
+
this.history.push(assistantMessage);
|
|
6466
|
+
this.trimHistory();
|
|
6467
|
+
this.emit('response', response.text);
|
|
6468
|
+
if (response.hasActions) {
|
|
6469
|
+
this.emit('actions', response.actionCalls);
|
|
6099
6470
|
}
|
|
6471
|
+
// Trigger reply prediction generation (fire and forget)
|
|
6472
|
+
this.triggerReplyPrediction();
|
|
6473
|
+
return response;
|
|
6474
|
+
}
|
|
6475
|
+
finally {
|
|
6476
|
+
this._isTalking = false;
|
|
6100
6477
|
}
|
|
6101
|
-
}
|
|
6102
|
-
// ===== Player Description =====
|
|
6103
|
-
/**
|
|
6104
|
-
* Set the player's description for AI context.
|
|
6105
|
-
* Used when generating reply predictions and for NPC context.
|
|
6106
|
-
* @param description Description of the player character
|
|
6107
|
-
*/
|
|
6108
|
-
setPlayerDescription(description) {
|
|
6109
|
-
this.playerDescription = description;
|
|
6110
|
-
this.emit('playerDescriptionChanged', description);
|
|
6111
|
-
}
|
|
6112
|
-
/**
|
|
6113
|
-
* Get the current player description.
|
|
6114
|
-
* @returns The player description, or null if not set
|
|
6115
|
-
*/
|
|
6116
|
-
getPlayerDescription() {
|
|
6117
|
-
return this.playerDescription;
|
|
6118
6478
|
}
|
|
6119
6479
|
/**
|
|
6120
|
-
*
|
|
6480
|
+
* Talk to the NPC with actions (streaming)
|
|
6481
|
+
* Text streams first, action calls are returned in onComplete
|
|
6121
6482
|
*/
|
|
6122
|
-
|
|
6123
|
-
this.
|
|
6124
|
-
|
|
6483
|
+
async talkWithActionsStream(message, actions, onChunk, onComplete) {
|
|
6484
|
+
this._isTalking = true;
|
|
6485
|
+
try {
|
|
6486
|
+
// Add user message to history
|
|
6487
|
+
const userMessage = { role: 'user', content: message };
|
|
6488
|
+
this.history.push(userMessage);
|
|
6489
|
+
// Convert NpcActions to ChatTools
|
|
6490
|
+
const tools = actions
|
|
6491
|
+
.filter(a => a && a.enabled !== false)
|
|
6492
|
+
.map(a => npcActionToTool(a));
|
|
6493
|
+
// Build messages array with system prompt
|
|
6494
|
+
const messages = [
|
|
6495
|
+
{ role: 'system', content: this.buildSystemPrompt() },
|
|
6496
|
+
...this.history,
|
|
6497
|
+
];
|
|
6498
|
+
// Generate response with tools (streaming)
|
|
6499
|
+
await this.chatClient.textGenerationWithToolsStream({
|
|
6500
|
+
messages,
|
|
6501
|
+
temperature: this.temperature,
|
|
6502
|
+
tools,
|
|
6503
|
+
tool_choice: 'auto',
|
|
6504
|
+
onChunk,
|
|
6505
|
+
onComplete: (result) => {
|
|
6506
|
+
this._isTalking = false;
|
|
6507
|
+
// Build response
|
|
6508
|
+
const response = {
|
|
6509
|
+
text: result.content || '',
|
|
6510
|
+
actionCalls: [],
|
|
6511
|
+
hasActions: false,
|
|
6512
|
+
};
|
|
6513
|
+
// Extract tool calls if any
|
|
6514
|
+
if (result.tool_calls) {
|
|
6515
|
+
response.actionCalls = result.tool_calls.map(tc => ({
|
|
6516
|
+
id: tc.id,
|
|
6517
|
+
actionName: tc.function.name,
|
|
6518
|
+
arguments: this.parseToolArguments(tc.function.arguments),
|
|
6519
|
+
}));
|
|
6520
|
+
response.hasActions = response.actionCalls.length > 0;
|
|
6521
|
+
}
|
|
6522
|
+
// Add assistant response to history
|
|
6523
|
+
const assistantMessage = {
|
|
6524
|
+
role: 'assistant',
|
|
6525
|
+
content: response.text,
|
|
6526
|
+
tool_calls: result.tool_calls,
|
|
6527
|
+
};
|
|
6528
|
+
this.history.push(assistantMessage);
|
|
6529
|
+
this.trimHistory();
|
|
6530
|
+
this.emit('response', response.text);
|
|
6531
|
+
if (response.hasActions) {
|
|
6532
|
+
this.emit('actions', response.actionCalls);
|
|
6533
|
+
}
|
|
6534
|
+
// Trigger reply prediction generation (fire and forget)
|
|
6535
|
+
this.triggerReplyPrediction();
|
|
6536
|
+
if (onComplete) {
|
|
6537
|
+
onComplete(response);
|
|
6538
|
+
}
|
|
6539
|
+
},
|
|
6540
|
+
});
|
|
6541
|
+
}
|
|
6542
|
+
catch (error) {
|
|
6543
|
+
this._isTalking = false;
|
|
6544
|
+
throw error;
|
|
6545
|
+
}
|
|
6125
6546
|
}
|
|
6126
|
-
// =====
|
|
6547
|
+
// ===== Action Results Reporting =====
|
|
6127
6548
|
/**
|
|
6128
|
-
*
|
|
6129
|
-
*
|
|
6549
|
+
* Report action results back to the conversation
|
|
6550
|
+
* Call this after executing actions to let the NPC know the results
|
|
6130
6551
|
*/
|
|
6131
|
-
|
|
6132
|
-
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
isCompacted: false,
|
|
6138
|
-
compactionCount: 0,
|
|
6552
|
+
reportActionResults(results) {
|
|
6553
|
+
for (const [callId, result] of Object.entries(results)) {
|
|
6554
|
+
this.history.push({
|
|
6555
|
+
role: 'tool',
|
|
6556
|
+
tool_call_id: callId,
|
|
6557
|
+
content: result,
|
|
6139
6558
|
});
|
|
6140
6559
|
}
|
|
6141
6560
|
}
|
|
6142
6561
|
/**
|
|
6143
|
-
*
|
|
6144
|
-
* @param npc The NPC client to unregister
|
|
6562
|
+
* Report a single action result
|
|
6145
6563
|
*/
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6564
|
+
reportActionResult(callId, result) {
|
|
6565
|
+
this.history.push({
|
|
6566
|
+
role: 'tool',
|
|
6567
|
+
tool_call_id: callId,
|
|
6568
|
+
content: result,
|
|
6569
|
+
});
|
|
6150
6570
|
}
|
|
6151
6571
|
/**
|
|
6152
|
-
*
|
|
6153
|
-
* Called after each Talk() exchange.
|
|
6154
|
-
* @param npc The NPC client that had a conversation
|
|
6572
|
+
* Parse tool arguments from JSON string
|
|
6155
6573
|
*/
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
return;
|
|
6159
|
-
|
|
6160
|
-
|
|
6574
|
+
parseToolArguments(args) {
|
|
6575
|
+
try {
|
|
6576
|
+
return JSON.parse(args);
|
|
6577
|
+
}
|
|
6578
|
+
catch (_a) {
|
|
6579
|
+
return {};
|
|
6161
6580
|
}
|
|
6162
|
-
const state = this.npcStates.get(npc);
|
|
6163
|
-
state.lastConversationTime = new Date();
|
|
6164
|
-
state.isCompacted = false; // Reset compaction flag on new conversation
|
|
6165
6581
|
}
|
|
6582
|
+
// ===== Conversation History Management =====
|
|
6166
6583
|
/**
|
|
6167
|
-
* Get
|
|
6584
|
+
* Get conversation history
|
|
6168
6585
|
*/
|
|
6169
|
-
|
|
6170
|
-
return
|
|
6586
|
+
getHistory() {
|
|
6587
|
+
return [...this.history];
|
|
6171
6588
|
}
|
|
6172
6589
|
/**
|
|
6173
|
-
* Get the
|
|
6590
|
+
* Get the number of messages in history
|
|
6174
6591
|
*/
|
|
6175
|
-
|
|
6176
|
-
return this.
|
|
6592
|
+
getHistoryLength() {
|
|
6593
|
+
return this.history.length;
|
|
6177
6594
|
}
|
|
6178
|
-
// ===== Auto Compaction =====
|
|
6179
6595
|
/**
|
|
6180
|
-
*
|
|
6181
|
-
*
|
|
6182
|
-
* @returns True if eligible for compaction
|
|
6596
|
+
* Clear conversation history.
|
|
6597
|
+
* The character design and memories will be preserved.
|
|
6183
6598
|
*/
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
const state = this.npcStates.get(npc);
|
|
6188
|
-
if (!state)
|
|
6189
|
-
return false;
|
|
6190
|
-
// Check if already compacted since last conversation
|
|
6191
|
-
if (state.isCompacted)
|
|
6192
|
-
return false;
|
|
6193
|
-
// Check message count
|
|
6194
|
-
const history = npc.getHistory();
|
|
6195
|
-
const nonSystemMessages = history.filter(m => m.role !== 'system').length;
|
|
6196
|
-
if (nonSystemMessages < this.config.autoCompactMinMessages)
|
|
6197
|
-
return false;
|
|
6198
|
-
// Check time since last conversation
|
|
6199
|
-
const timeSinceLastConversation = (Date.now() - state.lastConversationTime.getTime()) / 1000;
|
|
6200
|
-
if (timeSinceLastConversation < this.config.autoCompactTimeoutSeconds)
|
|
6201
|
-
return false;
|
|
6202
|
-
return true;
|
|
6599
|
+
clearHistory() {
|
|
6600
|
+
this.history = [];
|
|
6601
|
+
this.emit('history_cleared');
|
|
6203
6602
|
}
|
|
6204
6603
|
/**
|
|
6205
|
-
*
|
|
6206
|
-
*
|
|
6207
|
-
* @param npc The NPC to compact
|
|
6208
|
-
* @returns True if compaction succeeded
|
|
6604
|
+
* Revert the last exchange (user message and assistant response) from history.
|
|
6605
|
+
* @returns true if reverted, false if not enough history
|
|
6209
6606
|
*/
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
this.logger.error('Cannot compact: No chat client factory set. Call setChatClientFactory() first.');
|
|
6217
|
-
return false;
|
|
6218
|
-
}
|
|
6219
|
-
const history = npc.getHistory();
|
|
6220
|
-
const nonSystemMessages = history.filter(m => m.role !== 'system');
|
|
6221
|
-
if (nonSystemMessages.length < 2) {
|
|
6222
|
-
this.logger.info('Skipping compaction: not enough messages');
|
|
6223
|
-
return false;
|
|
6224
|
-
}
|
|
6225
|
-
try {
|
|
6226
|
-
this.logger.info(`Starting compaction (${nonSystemMessages.length} messages)`);
|
|
6227
|
-
// Build conversation text for summarization
|
|
6228
|
-
const conversationText = nonSystemMessages
|
|
6229
|
-
.map(m => `${m.role}: ${m.content}`)
|
|
6230
|
-
.join('\n');
|
|
6231
|
-
// Create summarization prompt
|
|
6232
|
-
const summaryPrompt = `Summarize the following conversation concisely. Focus on:
|
|
6233
|
-
1. Key topics discussed
|
|
6234
|
-
2. Important information exchanged
|
|
6235
|
-
3. Any decisions or commitments made
|
|
6236
|
-
4. The emotional tone
|
|
6237
|
-
|
|
6238
|
-
Keep the summary under 200 words. Write in third person.
|
|
6239
|
-
|
|
6240
|
-
Conversation:
|
|
6241
|
-
${conversationText}`;
|
|
6242
|
-
// Use chat client for summarization
|
|
6243
|
-
const chatClient = this.chatClientFactory();
|
|
6244
|
-
const result = await chatClient.textGeneration({
|
|
6245
|
-
messages: [{ role: 'user', content: summaryPrompt }],
|
|
6246
|
-
temperature: 0.5,
|
|
6247
|
-
model: this.config.fastModel || undefined,
|
|
6248
|
-
});
|
|
6249
|
-
if (!result.content) {
|
|
6250
|
-
const error = 'Empty response from summarization';
|
|
6251
|
-
this.logger.error(`Compaction failed: ${error}`);
|
|
6252
|
-
this.emit('compactionFailed', npc, error);
|
|
6253
|
-
return false;
|
|
6607
|
+
revertHistory() {
|
|
6608
|
+
let lastAssistantIndex = -1;
|
|
6609
|
+
let lastUserIndex = -1;
|
|
6610
|
+
for (let i = this.history.length - 1; i >= 0; i--) {
|
|
6611
|
+
if (this.history[i].role === 'assistant' && lastAssistantIndex === -1) {
|
|
6612
|
+
lastAssistantIndex = i;
|
|
6254
6613
|
}
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
// Update state
|
|
6259
|
-
const state = this.npcStates.get(npc);
|
|
6260
|
-
if (state) {
|
|
6261
|
-
state.isCompacted = true;
|
|
6262
|
-
state.compactionCount++;
|
|
6614
|
+
else if (this.history[i].role === 'user' && lastAssistantIndex !== -1 && lastUserIndex === -1) {
|
|
6615
|
+
lastUserIndex = i;
|
|
6616
|
+
break;
|
|
6263
6617
|
}
|
|
6264
|
-
this.logger.info(`Compaction completed. Summary: ${result.content.substring(0, 100)}...`);
|
|
6265
|
-
this.emit('npcCompacted', npc);
|
|
6266
|
-
return true;
|
|
6267
6618
|
}
|
|
6268
|
-
|
|
6269
|
-
|
|
6270
|
-
this.
|
|
6271
|
-
this.
|
|
6272
|
-
|
|
6619
|
+
if (lastAssistantIndex !== -1 && lastUserIndex !== -1) {
|
|
6620
|
+
// Remove in reverse order to maintain indices
|
|
6621
|
+
this.history.splice(lastAssistantIndex, 1);
|
|
6622
|
+
this.history.splice(lastUserIndex, 1);
|
|
6623
|
+
this.emit('history_reverted');
|
|
6624
|
+
return true;
|
|
6273
6625
|
}
|
|
6626
|
+
return false;
|
|
6274
6627
|
}
|
|
6275
6628
|
/**
|
|
6276
|
-
*
|
|
6277
|
-
* @
|
|
6629
|
+
* Revert (remove) the last N chat messages from history
|
|
6630
|
+
* @param count Number of messages to remove
|
|
6631
|
+
* @returns Number of messages actually removed
|
|
6278
6632
|
*/
|
|
6279
|
-
|
|
6280
|
-
|
|
6281
|
-
if (eligibleNpcs.length === 0) {
|
|
6633
|
+
revertChatMessages(count) {
|
|
6634
|
+
if (count <= 0)
|
|
6282
6635
|
return 0;
|
|
6636
|
+
const messagesToRemove = Math.min(count, this.history.length);
|
|
6637
|
+
const originalCount = this.history.length;
|
|
6638
|
+
this.history = this.history.slice(0, -messagesToRemove);
|
|
6639
|
+
const actuallyRemoved = originalCount - this.history.length;
|
|
6640
|
+
if (actuallyRemoved > 0) {
|
|
6641
|
+
this.emit('history_reverted', actuallyRemoved);
|
|
6283
6642
|
}
|
|
6284
|
-
|
|
6285
|
-
let successCount = 0;
|
|
6286
|
-
for (const npc of eligibleNpcs) {
|
|
6287
|
-
const success = await this.compactConversation(npc);
|
|
6288
|
-
if (success)
|
|
6289
|
-
successCount++;
|
|
6290
|
-
}
|
|
6291
|
-
return successCount;
|
|
6643
|
+
return actuallyRemoved;
|
|
6292
6644
|
}
|
|
6293
|
-
// ===== Auto Compact Timer =====
|
|
6294
6645
|
/**
|
|
6295
|
-
*
|
|
6646
|
+
* Revert to a specific point in history
|
|
6647
|
+
* @deprecated Use revertHistory() or revertChatMessages() instead
|
|
6296
6648
|
*/
|
|
6297
|
-
|
|
6298
|
-
if (this.
|
|
6299
|
-
this.
|
|
6649
|
+
revertToMessage(index) {
|
|
6650
|
+
if (index >= 0 && index < this.history.length) {
|
|
6651
|
+
this.history = this.history.slice(0, index + 1);
|
|
6652
|
+
this.emit('history_reverted', index);
|
|
6300
6653
|
}
|
|
6301
|
-
this.autoCompactTimer = setInterval(() => {
|
|
6302
|
-
this.runAutoCompactCheck();
|
|
6303
|
-
}, this.config.autoCompactCheckInterval);
|
|
6304
6654
|
}
|
|
6305
6655
|
/**
|
|
6306
|
-
*
|
|
6656
|
+
* Append a message to history manually
|
|
6307
6657
|
*/
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
this.autoCompactTimer = null;
|
|
6312
|
-
}
|
|
6658
|
+
appendMessage(message) {
|
|
6659
|
+
this.history.push(message);
|
|
6660
|
+
this.trimHistory();
|
|
6313
6661
|
}
|
|
6314
6662
|
/**
|
|
6315
|
-
*
|
|
6663
|
+
* Alias for appendMessage (Unity SDK compatibility)
|
|
6316
6664
|
*/
|
|
6317
|
-
|
|
6318
|
-
if (!
|
|
6665
|
+
appendChatMessage(role, content) {
|
|
6666
|
+
if (!role || !content) {
|
|
6667
|
+
this.logger.warn('Role and content cannot be empty');
|
|
6319
6668
|
return;
|
|
6320
|
-
const eligibleNpcs = Array.from(this.npcStates.keys()).filter(npc => this.isEligibleForCompaction(npc));
|
|
6321
|
-
for (const npc of eligibleNpcs) {
|
|
6322
|
-
// Fire and forget - don't block
|
|
6323
|
-
this.compactConversation(npc).catch(err => {
|
|
6324
|
-
this.logger.error('Auto-compact error:', err);
|
|
6325
|
-
});
|
|
6326
6669
|
}
|
|
6670
|
+
this.appendMessage({ role: role, content });
|
|
6327
6671
|
}
|
|
6328
|
-
// ===== Lifecycle =====
|
|
6329
6672
|
/**
|
|
6330
|
-
*
|
|
6673
|
+
* Trim history to max length
|
|
6331
6674
|
*/
|
|
6332
|
-
|
|
6333
|
-
this.
|
|
6334
|
-
|
|
6675
|
+
trimHistory() {
|
|
6676
|
+
if (this.history.length > this.maxHistoryLength) {
|
|
6677
|
+
// Keep the most recent messages
|
|
6678
|
+
this.history = this.history.slice(-this.maxHistoryLength);
|
|
6679
|
+
}
|
|
6335
6680
|
}
|
|
6681
|
+
// ===== Save/Load =====
|
|
6336
6682
|
/**
|
|
6337
|
-
*
|
|
6683
|
+
* Save the current conversation history to a serializable format.
|
|
6684
|
+
* Includes characterDesign, memories, and history.
|
|
6338
6685
|
*/
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6686
|
+
saveHistory() {
|
|
6687
|
+
const saveData = {
|
|
6688
|
+
characterDesign: this.characterDesign,
|
|
6689
|
+
memories: Array.from(this.memories.entries()).map(([name, content]) => ({ name, content })),
|
|
6690
|
+
history: this.history,
|
|
6691
|
+
};
|
|
6692
|
+
return JSON.stringify(saveData);
|
|
6342
6693
|
}
|
|
6343
6694
|
/**
|
|
6344
|
-
*
|
|
6695
|
+
* Load conversation history from serialized data.
|
|
6696
|
+
* Restores characterDesign, memories, and history.
|
|
6345
6697
|
*/
|
|
6346
|
-
|
|
6347
|
-
|
|
6348
|
-
|
|
6349
|
-
|
|
6350
|
-
|
|
6698
|
+
loadHistory(saveData) {
|
|
6699
|
+
try {
|
|
6700
|
+
const data = JSON.parse(saveData);
|
|
6701
|
+
// Load character design (with backwards compatibility for old systemPrompt field)
|
|
6702
|
+
this.characterDesign = data.characterDesign || data.systemPrompt || this.characterDesign;
|
|
6703
|
+
// Load memories
|
|
6704
|
+
this.memories.clear();
|
|
6705
|
+
if (data.memories && Array.isArray(data.memories)) {
|
|
6706
|
+
for (const memory of data.memories) {
|
|
6707
|
+
if (memory.name && memory.content) {
|
|
6708
|
+
this.memories.set(memory.name, memory.content);
|
|
6709
|
+
}
|
|
6710
|
+
}
|
|
6711
|
+
}
|
|
6712
|
+
// Load history (skip system messages as they'll be rebuilt from characterDesign + memories)
|
|
6713
|
+
this.history = (data.history || []).filter(m => m.role !== 'system');
|
|
6714
|
+
this.emit('history_loaded');
|
|
6715
|
+
return true;
|
|
6716
|
+
}
|
|
6717
|
+
catch (error) {
|
|
6718
|
+
this.logger.error('Failed to load history:', error);
|
|
6719
|
+
return false;
|
|
6720
|
+
}
|
|
6351
6721
|
}
|
|
6352
6722
|
}
|
|
6353
|
-
AIContextManager._instance = null;
|
|
6354
|
-
/**
|
|
6355
|
-
* Default AIContextManager instance
|
|
6356
|
-
* Can be used as a global context manager
|
|
6357
|
-
*/
|
|
6358
|
-
const defaultContextManager = AIContextManager.getInstance();
|
|
6359
6723
|
|
|
6360
6724
|
/**
|
|
6361
6725
|
* Schema Library for managing JSON schemas for AI structured output generation
|
|
@@ -6567,10 +6931,12 @@ ${conversationText}`;
|
|
|
6567
6931
|
this.chatProvider = new ChatProvider(this.authManager, this.config);
|
|
6568
6932
|
this.imageProvider = new ImageProvider(this.authManager, this.config);
|
|
6569
6933
|
this.transcriptionProvider = new TranscriptionProvider(this.authManager, this.config);
|
|
6934
|
+
this.ttsProvider = new TTSProvider(this.authManager, this.config);
|
|
6570
6935
|
// Connect providers to player client for balance checking
|
|
6571
6936
|
this.chatProvider.setPlayerClient(this.playerClient);
|
|
6572
6937
|
this.imageProvider.setPlayerClient(this.playerClient);
|
|
6573
6938
|
this.transcriptionProvider.setPlayerClient(this.playerClient);
|
|
6939
|
+
this.ttsProvider.setPlayerClient(this.playerClient);
|
|
6574
6940
|
// Initialize AI context manager
|
|
6575
6941
|
this.contextManager = new AIContextManager(this.config.aiContext);
|
|
6576
6942
|
// Set chat client factory for compaction
|
|
@@ -6714,20 +7080,20 @@ ${conversationText}`;
|
|
|
6714
7080
|
// Create indicator element
|
|
6715
7081
|
this.devTokenIndicator = document.createElement('div');
|
|
6716
7082
|
this.devTokenIndicator.textContent = 'DeveloperToken';
|
|
6717
|
-
this.devTokenIndicator.style.cssText = `
|
|
6718
|
-
position: fixed;
|
|
6719
|
-
top: 10px;
|
|
6720
|
-
left: 10px;
|
|
6721
|
-
background-color: #dc2626;
|
|
6722
|
-
color: white;
|
|
6723
|
-
padding: 4px 12px;
|
|
6724
|
-
border-radius: 4px;
|
|
6725
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
6726
|
-
font-size: 12px;
|
|
6727
|
-
font-weight: 600;
|
|
6728
|
-
z-index: 999999;
|
|
6729
|
-
pointer-events: none;
|
|
6730
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
7083
|
+
this.devTokenIndicator.style.cssText = `
|
|
7084
|
+
position: fixed;
|
|
7085
|
+
top: 10px;
|
|
7086
|
+
left: 10px;
|
|
7087
|
+
background-color: #dc2626;
|
|
7088
|
+
color: white;
|
|
7089
|
+
padding: 4px 12px;
|
|
7090
|
+
border-radius: 4px;
|
|
7091
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
7092
|
+
font-size: 12px;
|
|
7093
|
+
font-weight: 600;
|
|
7094
|
+
z-index: 999999;
|
|
7095
|
+
pointer-events: none;
|
|
7096
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
6731
7097
|
`;
|
|
6732
7098
|
document.body.appendChild(this.devTokenIndicator);
|
|
6733
7099
|
}
|
|
@@ -6819,6 +7185,14 @@ ${conversationText}`;
|
|
|
6819
7185
|
this.ensureInitialized();
|
|
6820
7186
|
return new TranscriptionClient(this.transcriptionProvider, model || this.config.defaultTranscriptionModel);
|
|
6821
7187
|
}
|
|
7188
|
+
/**
|
|
7189
|
+
* Create a TTS client for text-to-speech
|
|
7190
|
+
* @param model - TTS model to use (default: 'default-tts-model')
|
|
7191
|
+
*/
|
|
7192
|
+
createTTSClient(model) {
|
|
7193
|
+
this.ensureInitialized();
|
|
7194
|
+
return new TTSClient(this.ttsProvider, model || this.config.defaultTTSModel);
|
|
7195
|
+
}
|
|
6822
7196
|
/**
|
|
6823
7197
|
* Create an NPC client
|
|
6824
7198
|
* Automatically registers with AIContextManager
|
|
@@ -6890,7 +7264,7 @@ ${conversationText}`;
|
|
|
6890
7264
|
*/
|
|
6891
7265
|
setDebug(enabled) {
|
|
6892
7266
|
this.config.debug = enabled;
|
|
6893
|
-
Logger.setGlobalLevel(enabled ?
|
|
7267
|
+
Logger.setGlobalLevel(enabled ? LogLevel.DEBUG : LogLevel.WARN);
|
|
6894
7268
|
}
|
|
6895
7269
|
/**
|
|
6896
7270
|
* Configure the logging system
|
|
@@ -6912,7 +7286,7 @@ ${conversationText}`;
|
|
|
6912
7286
|
initializeLogging(config) {
|
|
6913
7287
|
// Handle legacy debug option for backwards compatibility
|
|
6914
7288
|
if (config.debug !== undefined && config.logging === undefined) {
|
|
6915
|
-
Logger.setGlobalLevel(config.debug ?
|
|
7289
|
+
Logger.setGlobalLevel(config.debug ? LogLevel.DEBUG : LogLevel.WARN);
|
|
6916
7290
|
}
|
|
6917
7291
|
// Apply new logging config
|
|
6918
7292
|
if (config.logging) {
|
|
@@ -7116,9 +7490,7 @@ ${conversationText}`;
|
|
|
7116
7490
|
*/
|
|
7117
7491
|
async validateToken(token, gameId) {
|
|
7118
7492
|
var _a, _b;
|
|
7119
|
-
const headers = {
|
|
7120
|
-
'Authorization': `Bearer ${token}`,
|
|
7121
|
-
};
|
|
7493
|
+
const headers = Object.assign({ 'Authorization': `Bearer ${token}` }, getSDKHeaders());
|
|
7122
7494
|
if (gameId) {
|
|
7123
7495
|
headers['X-Game-Id'] = gameId;
|
|
7124
7496
|
}
|
|
@@ -7148,9 +7520,7 @@ ${conversationText}`;
|
|
|
7148
7520
|
*/
|
|
7149
7521
|
async verifyToken(token, gameId) {
|
|
7150
7522
|
var _a, _b;
|
|
7151
|
-
const headers = {
|
|
7152
|
-
'Authorization': `Bearer ${token}`,
|
|
7153
|
-
};
|
|
7523
|
+
const headers = Object.assign({ 'Authorization': `Bearer ${token}` }, getSDKHeaders());
|
|
7154
7524
|
if (gameId) {
|
|
7155
7525
|
headers['X-Game-Id'] = gameId;
|
|
7156
7526
|
}
|
|
@@ -7213,36 +7583,62 @@ ${conversationText}`;
|
|
|
7213
7583
|
*/
|
|
7214
7584
|
const defaultTokenValidator = new TokenValidator();
|
|
7215
7585
|
|
|
7216
|
-
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7240
|
-
|
|
7241
|
-
|
|
7242
|
-
|
|
7243
|
-
|
|
7244
|
-
|
|
7245
|
-
|
|
7586
|
+
/**
|
|
7587
|
+
* PlayKit SDK for JavaScript
|
|
7588
|
+
* AI integration for web-based games
|
|
7589
|
+
*/
|
|
7590
|
+
// Main SDK
|
|
7591
|
+
|
|
7592
|
+
var namespace = /*#__PURE__*/Object.freeze({
|
|
7593
|
+
__proto__: null,
|
|
7594
|
+
AIContextManager: AIContextManager,
|
|
7595
|
+
AuthFlowManager: AuthFlowManager,
|
|
7596
|
+
AuthManager: AuthManager,
|
|
7597
|
+
BrowserStorage: BrowserStorage,
|
|
7598
|
+
BufferLogHandler: BufferLogHandler,
|
|
7599
|
+
CallbackLogHandler: CallbackLogHandler,
|
|
7600
|
+
ChatClient: ChatClient,
|
|
7601
|
+
DeviceAuthFlowManager: DeviceAuthFlowManager,
|
|
7602
|
+
ImageClient: ImageClient,
|
|
7603
|
+
get LogLevel () { return LogLevel; },
|
|
7604
|
+
Logger: Logger,
|
|
7605
|
+
MemoryStorage: MemoryStorage,
|
|
7606
|
+
NPCClient: NPCClient,
|
|
7607
|
+
PlayKitSDK: PlayKitSDK,
|
|
7608
|
+
PlayerClient: PlayerClient,
|
|
7609
|
+
RechargeManager: RechargeManager,
|
|
7610
|
+
SchemaLibrary: SchemaLibrary,
|
|
7611
|
+
StreamParser: StreamParser,
|
|
7612
|
+
TTSClient: TTSClient,
|
|
7613
|
+
TokenStorage: TokenStorage,
|
|
7614
|
+
TokenValidator: TokenValidator,
|
|
7615
|
+
TranscriptionClient: TranscriptionClient,
|
|
7616
|
+
createMultimodalMessage: createMultimodalMessage,
|
|
7617
|
+
createStorage: createStorage,
|
|
7618
|
+
createTextMessage: createTextMessage,
|
|
7619
|
+
default: PlayKitSDK,
|
|
7620
|
+
defaultContextManager: defaultContextManager,
|
|
7621
|
+
defaultSchemaLibrary: defaultSchemaLibrary,
|
|
7622
|
+
defaultTokenValidator: defaultTokenValidator,
|
|
7623
|
+
isLocalStorageAvailable: isLocalStorageAvailable
|
|
7624
|
+
});
|
|
7625
|
+
|
|
7626
|
+
/**
|
|
7627
|
+
* UMD bundle entry.
|
|
7628
|
+
*
|
|
7629
|
+
* Goal: make `window.PlayKitSDK` directly the constructor class while still
|
|
7630
|
+
* exposing every named export as a property on it.
|
|
7631
|
+
*
|
|
7632
|
+
* Result:
|
|
7633
|
+
* new window.PlayKitSDK(cfg) // recommended
|
|
7634
|
+
* new window.PlayKitSDK.PlayKitSDK(cfg) // legacy v1.x form, still works
|
|
7635
|
+
* window.PlayKitSDK.ChatClient // named exports preserved
|
|
7636
|
+
*/
|
|
7637
|
+
Object.assign(PlayKitSDK, namespace);
|
|
7638
|
+
PlayKitSDK.PlayKitSDK = PlayKitSDK;
|
|
7639
|
+
PlayKitSDK.default = PlayKitSDK;
|
|
7640
|
+
|
|
7641
|
+
return PlayKitSDK;
|
|
7246
7642
|
|
|
7247
7643
|
}));
|
|
7248
7644
|
//# sourceMappingURL=playkit-sdk.umd.js.map
|