playkit-sdk 1.2.12 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +86 -86
- package/README.md +246 -244
- package/dist/playkit-sdk.cjs.js +1723 -1530
- package/dist/playkit-sdk.cjs.js.map +1 -1
- package/dist/playkit-sdk.d.ts +66 -5
- package/dist/playkit-sdk.esm.js +1723 -1530
- package/dist/playkit-sdk.esm.js.map +1 -1
- package/dist/playkit-sdk.umd.js +1789 -1571
- package/dist/playkit-sdk.umd.js.map +1 -1
- package/package.json +70 -70
package/dist/playkit-sdk.cjs.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* playkit-sdk v1.
|
|
2
|
+
* playkit-sdk v1.3.0
|
|
3
3
|
* PlayKit SDK for JavaScript
|
|
4
4
|
* @license SEE LICENSE IN LICENSE
|
|
5
5
|
*/
|
|
@@ -829,6 +829,15 @@ class TokenStorage {
|
|
|
829
829
|
}
|
|
830
830
|
}
|
|
831
831
|
|
|
832
|
+
const SDK_TYPE = 'Javascript';
|
|
833
|
+
const SDK_VERSION = '"1.3.0"';
|
|
834
|
+
function getSDKHeaders() {
|
|
835
|
+
return {
|
|
836
|
+
'X-SDK-Type': SDK_TYPE,
|
|
837
|
+
'X-SDK-Version': SDK_VERSION,
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
|
|
832
841
|
/**
|
|
833
842
|
* Authentication Flow Manager
|
|
834
843
|
* Manages the headless authentication flow with automatic UI
|
|
@@ -933,8 +942,8 @@ class AuthFlowManager extends EventEmitter {
|
|
|
933
942
|
constructor(baseURL) {
|
|
934
943
|
super();
|
|
935
944
|
this.currentSessionId = null;
|
|
936
|
-
this.
|
|
937
|
-
this.
|
|
945
|
+
this._uiContainer = null;
|
|
946
|
+
this._isSuccess = false;
|
|
938
947
|
this.currentLanguage = 'en';
|
|
939
948
|
// UI Elements
|
|
940
949
|
this.modal = null;
|
|
@@ -1015,84 +1024,84 @@ class AuthFlowManager extends EventEmitter {
|
|
|
1015
1024
|
// Create modal container
|
|
1016
1025
|
this.modal = document.createElement('div');
|
|
1017
1026
|
this.modal.className = 'playkit-auth-modal';
|
|
1018
|
-
this.modal.innerHTML = `
|
|
1019
|
-
<div class="playkit-auth-overlay"></div>
|
|
1020
|
-
<div class="playkit-auth-container">
|
|
1021
|
-
<!-- Identifier Panel -->
|
|
1022
|
-
<div class="playkit-auth-panel" id="playkit-identifier-panel">
|
|
1023
|
-
<div class="playkit-auth-header">
|
|
1024
|
-
<h2>${this.t('signIn')}</h2>
|
|
1025
|
-
<p>${this.t('signInSubtitle')}</p>
|
|
1026
|
-
</div>
|
|
1027
|
-
|
|
1028
|
-
<div class="playkit-auth-toggle">
|
|
1029
|
-
<label class="playkit-toggle-option">
|
|
1030
|
-
<input type="radio" name="auth-type" value="email" checked>
|
|
1031
|
-
<span>${this.t('email')}</span>
|
|
1032
|
-
</label>
|
|
1033
|
-
<label class="playkit-toggle-option">
|
|
1034
|
-
<input type="radio" name="auth-type" value="phone">
|
|
1035
|
-
<span>${this.t('phone')}</span>
|
|
1036
|
-
</label>
|
|
1037
|
-
</div>
|
|
1038
|
-
|
|
1039
|
-
<div class="playkit-auth-input-group">
|
|
1040
|
-
<div class="playkit-input-wrapper">
|
|
1041
|
-
<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">
|
|
1042
|
-
<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>
|
|
1043
|
-
<polyline points="22,6 12,13 2,6"></polyline>
|
|
1044
|
-
</svg>
|
|
1045
|
-
<input
|
|
1046
|
-
type="text"
|
|
1047
|
-
id="playkit-identifier-input"
|
|
1048
|
-
placeholder="${this.t('emailPlaceholder')}"
|
|
1049
|
-
autocomplete="off"
|
|
1050
|
-
>
|
|
1051
|
-
</div>
|
|
1052
|
-
</div>
|
|
1053
|
-
|
|
1054
|
-
<button class="playkit-auth-button" id="playkit-send-code-btn">
|
|
1055
|
-
${this.t('sendCode')}
|
|
1056
|
-
</button>
|
|
1057
|
-
|
|
1058
|
-
<div class="playkit-auth-error" id="playkit-error-text"></div>
|
|
1059
|
-
</div>
|
|
1060
|
-
|
|
1061
|
-
<!-- Verification Panel -->
|
|
1062
|
-
<div class="playkit-auth-panel" id="playkit-verification-panel" style="display: none;">
|
|
1063
|
-
<div class="playkit-auth-header">
|
|
1064
|
-
<button class="playkit-back-button" id="playkit-back-btn">
|
|
1065
|
-
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1066
|
-
<path d="M19 12H5M12 19l-7-7 7-7"/>
|
|
1067
|
-
</svg>
|
|
1068
|
-
</button>
|
|
1069
|
-
<h2>${this.t('enterCode')}</h2>
|
|
1070
|
-
<p>${this.t('enterCodeSubtitle')} <span id="playkit-identifier-display"></span></p>
|
|
1071
|
-
</div>
|
|
1072
|
-
|
|
1073
|
-
<div class="playkit-auth-input-group">
|
|
1074
|
-
<div class="playkit-code-inputs">
|
|
1075
|
-
<input type="number" maxlength="1" class="playkit-code-input" data-index="0">
|
|
1076
|
-
<input type="number" maxlength="1" class="playkit-code-input" data-index="1">
|
|
1077
|
-
<input type="number" maxlength="1" class="playkit-code-input" data-index="2">
|
|
1078
|
-
<input type="number" maxlength="1" class="playkit-code-input" data-index="3">
|
|
1079
|
-
<input type="number" maxlength="1" class="playkit-code-input" data-index="4">
|
|
1080
|
-
<input type="number" maxlength="1" class="playkit-code-input" data-index="5">
|
|
1081
|
-
</div>
|
|
1082
|
-
</div>
|
|
1083
|
-
|
|
1084
|
-
<button class="playkit-auth-button" id="playkit-verify-btn">
|
|
1085
|
-
${this.t('verify')}
|
|
1086
|
-
</button>
|
|
1087
|
-
|
|
1088
|
-
<div class="playkit-auth-error" id="playkit-verify-error-text"></div>
|
|
1089
|
-
</div>
|
|
1090
|
-
|
|
1091
|
-
<!-- Loading Overlay -->
|
|
1092
|
-
<div class="playkit-loading-overlay" id="playkit-loading-overlay" style="display: none;">
|
|
1093
|
-
<div class="playkit-spinner"></div>
|
|
1094
|
-
</div>
|
|
1095
|
-
</div>
|
|
1027
|
+
this.modal.innerHTML = `
|
|
1028
|
+
<div class="playkit-auth-overlay"></div>
|
|
1029
|
+
<div class="playkit-auth-container">
|
|
1030
|
+
<!-- Identifier Panel -->
|
|
1031
|
+
<div class="playkit-auth-panel" id="playkit-identifier-panel">
|
|
1032
|
+
<div class="playkit-auth-header">
|
|
1033
|
+
<h2>${this.t('signIn')}</h2>
|
|
1034
|
+
<p>${this.t('signInSubtitle')}</p>
|
|
1035
|
+
</div>
|
|
1036
|
+
|
|
1037
|
+
<div class="playkit-auth-toggle">
|
|
1038
|
+
<label class="playkit-toggle-option">
|
|
1039
|
+
<input type="radio" name="auth-type" value="email" checked>
|
|
1040
|
+
<span>${this.t('email')}</span>
|
|
1041
|
+
</label>
|
|
1042
|
+
<label class="playkit-toggle-option">
|
|
1043
|
+
<input type="radio" name="auth-type" value="phone">
|
|
1044
|
+
<span>${this.t('phone')}</span>
|
|
1045
|
+
</label>
|
|
1046
|
+
</div>
|
|
1047
|
+
|
|
1048
|
+
<div class="playkit-auth-input-group">
|
|
1049
|
+
<div class="playkit-input-wrapper">
|
|
1050
|
+
<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">
|
|
1051
|
+
<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>
|
|
1052
|
+
<polyline points="22,6 12,13 2,6"></polyline>
|
|
1053
|
+
</svg>
|
|
1054
|
+
<input
|
|
1055
|
+
type="text"
|
|
1056
|
+
id="playkit-identifier-input"
|
|
1057
|
+
placeholder="${this.t('emailPlaceholder')}"
|
|
1058
|
+
autocomplete="off"
|
|
1059
|
+
>
|
|
1060
|
+
</div>
|
|
1061
|
+
</div>
|
|
1062
|
+
|
|
1063
|
+
<button class="playkit-auth-button" id="playkit-send-code-btn">
|
|
1064
|
+
${this.t('sendCode')}
|
|
1065
|
+
</button>
|
|
1066
|
+
|
|
1067
|
+
<div class="playkit-auth-error" id="playkit-error-text"></div>
|
|
1068
|
+
</div>
|
|
1069
|
+
|
|
1070
|
+
<!-- Verification Panel -->
|
|
1071
|
+
<div class="playkit-auth-panel" id="playkit-verification-panel" style="display: none;">
|
|
1072
|
+
<div class="playkit-auth-header">
|
|
1073
|
+
<button class="playkit-back-button" id="playkit-back-btn">
|
|
1074
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1075
|
+
<path d="M19 12H5M12 19l-7-7 7-7"/>
|
|
1076
|
+
</svg>
|
|
1077
|
+
</button>
|
|
1078
|
+
<h2>${this.t('enterCode')}</h2>
|
|
1079
|
+
<p>${this.t('enterCodeSubtitle')} <span id="playkit-identifier-display"></span></p>
|
|
1080
|
+
</div>
|
|
1081
|
+
|
|
1082
|
+
<div class="playkit-auth-input-group">
|
|
1083
|
+
<div class="playkit-code-inputs">
|
|
1084
|
+
<input type="number" maxlength="1" class="playkit-code-input" data-index="0">
|
|
1085
|
+
<input type="number" maxlength="1" class="playkit-code-input" data-index="1">
|
|
1086
|
+
<input type="number" maxlength="1" class="playkit-code-input" data-index="2">
|
|
1087
|
+
<input type="number" maxlength="1" class="playkit-code-input" data-index="3">
|
|
1088
|
+
<input type="number" maxlength="1" class="playkit-code-input" data-index="4">
|
|
1089
|
+
<input type="number" maxlength="1" class="playkit-code-input" data-index="5">
|
|
1090
|
+
</div>
|
|
1091
|
+
</div>
|
|
1092
|
+
|
|
1093
|
+
<button class="playkit-auth-button" id="playkit-verify-btn">
|
|
1094
|
+
${this.t('verify')}
|
|
1095
|
+
</button>
|
|
1096
|
+
|
|
1097
|
+
<div class="playkit-auth-error" id="playkit-verify-error-text"></div>
|
|
1098
|
+
</div>
|
|
1099
|
+
|
|
1100
|
+
<!-- Loading Overlay -->
|
|
1101
|
+
<div class="playkit-loading-overlay" id="playkit-loading-overlay" style="display: none;">
|
|
1102
|
+
<div class="playkit-spinner"></div>
|
|
1103
|
+
</div>
|
|
1104
|
+
</div>
|
|
1096
1105
|
`;
|
|
1097
1106
|
// Add styles and load VanillaOTP
|
|
1098
1107
|
this.addStyles();
|
|
@@ -1127,274 +1136,274 @@ class AuthFlowManager extends EventEmitter {
|
|
|
1127
1136
|
return;
|
|
1128
1137
|
const style = document.createElement('style');
|
|
1129
1138
|
style.id = styleId;
|
|
1130
|
-
style.textContent = `
|
|
1131
|
-
.playkit-auth-modal {
|
|
1132
|
-
position: fixed;
|
|
1133
|
-
top: 0;
|
|
1134
|
-
left: 0;
|
|
1135
|
-
right: 0;
|
|
1136
|
-
bottom: 0;
|
|
1137
|
-
z-index: 999999;
|
|
1138
|
-
display: flex;
|
|
1139
|
-
justify-content: center;
|
|
1140
|
-
align-items: center;
|
|
1141
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
.playkit-auth-overlay {
|
|
1145
|
-
position: absolute;
|
|
1146
|
-
top: 0;
|
|
1147
|
-
left: 0;
|
|
1148
|
-
right: 0;
|
|
1149
|
-
bottom: 0;
|
|
1150
|
-
background: rgba(0, 0, 0, 0.8);
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
.playkit-auth-container {
|
|
1154
|
-
position: relative;
|
|
1155
|
-
background: #fff;
|
|
1156
|
-
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
1157
|
-
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
|
|
1158
|
-
width: 90%;
|
|
1159
|
-
max-width: 320px;
|
|
1160
|
-
overflow: hidden;
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
.playkit-auth-panel {
|
|
1164
|
-
padding: 24px;
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
.playkit-auth-header {
|
|
1168
|
-
text-align: center;
|
|
1169
|
-
margin-bottom: 20px;
|
|
1170
|
-
position: relative;
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
.playkit-auth-header h2 {
|
|
1174
|
-
margin: 0 0 8px 0;
|
|
1175
|
-
font-size: 14px;
|
|
1176
|
-
font-weight: 600;
|
|
1177
|
-
color: #171717;
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
.playkit-auth-header p {
|
|
1181
|
-
margin: 0;
|
|
1182
|
-
font-size: 14px;
|
|
1183
|
-
color: #666;
|
|
1184
|
-
line-height: 1.5;
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
.playkit-back-button {
|
|
1188
|
-
position: absolute;
|
|
1189
|
-
left: 0;
|
|
1190
|
-
top: 0;
|
|
1191
|
-
background: transparent;
|
|
1192
|
-
border: none;
|
|
1193
|
-
cursor: pointer;
|
|
1194
|
-
padding: 4px;
|
|
1195
|
-
color: #666;
|
|
1196
|
-
transition: background-color 0.2s ease, color 0.2s ease;
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
.playkit-back-button:hover {
|
|
1200
|
-
background: #f5f5f5;
|
|
1201
|
-
color: #171717;
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
.playkit-auth-toggle {
|
|
1205
|
-
display: flex;
|
|
1206
|
-
background: #f5f5f5;
|
|
1207
|
-
padding: 2px;
|
|
1208
|
-
margin-bottom: 20px;
|
|
1209
|
-
gap: 2px;
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
.playkit-toggle-option {
|
|
1213
|
-
flex: 1;
|
|
1214
|
-
display: flex;
|
|
1215
|
-
justify-content: center;
|
|
1216
|
-
align-items: center;
|
|
1217
|
-
padding: 10px 16px;
|
|
1218
|
-
cursor: pointer;
|
|
1219
|
-
transition: background-color 0.2s ease;
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
.playkit-toggle-option input {
|
|
1223
|
-
display: none;
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
.playkit-toggle-option span {
|
|
1227
|
-
font-size: 14px;
|
|
1228
|
-
font-weight: 500;
|
|
1229
|
-
color: #666;
|
|
1230
|
-
transition: color 0.2s ease;
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
.playkit-toggle-option input:checked + span {
|
|
1234
|
-
color: #fff;
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
|
-
.playkit-toggle-option:has(input:checked) {
|
|
1238
|
-
background: #171717;
|
|
1239
|
-
}
|
|
1240
|
-
|
|
1241
|
-
.playkit-auth-input-group {
|
|
1242
|
-
margin-bottom: 20px;
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
|
-
.playkit-input-wrapper {
|
|
1246
|
-
position: relative;
|
|
1247
|
-
display: flex;
|
|
1248
|
-
align-items: center;
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
.playkit-input-icon {
|
|
1252
|
-
position: absolute;
|
|
1253
|
-
left: 12px;
|
|
1254
|
-
color: #999;
|
|
1255
|
-
pointer-events: none;
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
.playkit-input-wrapper input {
|
|
1259
|
-
width: 100%;
|
|
1260
|
-
padding: 10px 12px 10px 44px;
|
|
1261
|
-
border: 1px solid #e5e7eb;
|
|
1262
|
-
font-size: 14px;
|
|
1263
|
-
transition: border-color 0.2s ease;
|
|
1264
|
-
box-sizing: border-box;
|
|
1265
|
-
background: #fff;
|
|
1266
|
-
}
|
|
1267
|
-
|
|
1268
|
-
.playkit-input-wrapper input:hover {
|
|
1269
|
-
border-color: #d4d4d4;
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
|
-
.playkit-input-wrapper input:focus {
|
|
1273
|
-
outline: none;
|
|
1274
|
-
border-color: #171717;
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
.playkit-code-inputs {
|
|
1278
|
-
display: flex;
|
|
1279
|
-
gap: 8px;
|
|
1280
|
-
justify-content: center;
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
.playkit-code-input {
|
|
1284
|
-
width: 40px !important;
|
|
1285
|
-
height: 48px;
|
|
1286
|
-
text-align: center;
|
|
1287
|
-
font-size: 20px;
|
|
1288
|
-
font-weight: 600;
|
|
1289
|
-
border: 1px solid #e5e7eb !important;
|
|
1290
|
-
padding: 0 !important;
|
|
1291
|
-
transition: border-color 0.2s ease;
|
|
1292
|
-
background: #fff;
|
|
1293
|
-
-moz-appearance: textfield;
|
|
1294
|
-
}
|
|
1295
|
-
|
|
1296
|
-
.playkit-code-input::-webkit-outer-spin-button,
|
|
1297
|
-
.playkit-code-input::-webkit-inner-spin-button {
|
|
1298
|
-
-webkit-appearance: none;
|
|
1299
|
-
margin: 0;
|
|
1300
|
-
}
|
|
1301
|
-
|
|
1302
|
-
.playkit-code-input:hover {
|
|
1303
|
-
border-color: #d4d4d4 !important;
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
.playkit-code-input:focus {
|
|
1307
|
-
outline: none;
|
|
1308
|
-
border-color: #171717 !important;
|
|
1309
|
-
}
|
|
1310
|
-
|
|
1311
|
-
.playkit-auth-button {
|
|
1312
|
-
width: 100%;
|
|
1313
|
-
padding: 10px 16px;
|
|
1314
|
-
background: #171717;
|
|
1315
|
-
color: white;
|
|
1316
|
-
border: none;
|
|
1317
|
-
font-size: 14px;
|
|
1318
|
-
font-weight: 500;
|
|
1319
|
-
cursor: pointer;
|
|
1320
|
-
transition: background 0.2s ease;
|
|
1321
|
-
}
|
|
1322
|
-
|
|
1323
|
-
.playkit-auth-button:hover:not(:disabled) {
|
|
1324
|
-
background: #404040;
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
.playkit-auth-button:active:not(:disabled) {
|
|
1328
|
-
background: #0a0a0a;
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
|
-
.playkit-auth-button:disabled {
|
|
1332
|
-
background: #e5e7eb;
|
|
1333
|
-
color: #999;
|
|
1334
|
-
cursor: not-allowed;
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
.playkit-auth-error {
|
|
1338
|
-
margin-top: 16px;
|
|
1339
|
-
padding: 12px 16px;
|
|
1340
|
-
background: #fef2f2;
|
|
1341
|
-
border: 1px solid #fecaca;
|
|
1342
|
-
color: #dc2626;
|
|
1343
|
-
font-size: 13px;
|
|
1344
|
-
text-align: left;
|
|
1345
|
-
display: none;
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
|
-
.playkit-auth-error.show {
|
|
1349
|
-
display: block;
|
|
1350
|
-
}
|
|
1351
|
-
|
|
1352
|
-
.playkit-loading-overlay {
|
|
1353
|
-
position: absolute;
|
|
1354
|
-
top: 0;
|
|
1355
|
-
left: 0;
|
|
1356
|
-
right: 0;
|
|
1357
|
-
bottom: 0;
|
|
1358
|
-
background: rgba(255, 255, 255, 0.96);
|
|
1359
|
-
display: flex;
|
|
1360
|
-
justify-content: center;
|
|
1361
|
-
align-items: center;
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
.playkit-spinner {
|
|
1365
|
-
width: 24px;
|
|
1366
|
-
height: 24px;
|
|
1367
|
-
border: 2px solid #e5e7eb;
|
|
1368
|
-
border-top: 2px solid #171717;
|
|
1369
|
-
border-radius: 50%;
|
|
1370
|
-
animation: playkit-spin 1s linear infinite;
|
|
1371
|
-
}
|
|
1372
|
-
|
|
1373
|
-
@keyframes playkit-spin {
|
|
1374
|
-
0% { transform: rotate(0deg); }
|
|
1375
|
-
100% { transform: rotate(360deg); }
|
|
1376
|
-
}
|
|
1377
|
-
|
|
1378
|
-
@media (max-width: 480px) {
|
|
1379
|
-
.playkit-auth-container {
|
|
1380
|
-
width: 95%;
|
|
1381
|
-
max-width: none;
|
|
1382
|
-
}
|
|
1383
|
-
|
|
1384
|
-
.playkit-auth-panel {
|
|
1385
|
-
padding: 20px;
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
|
-
.playkit-code-input {
|
|
1389
|
-
width: 36px !important;
|
|
1390
|
-
height: 44px;
|
|
1391
|
-
font-size: 18px;
|
|
1392
|
-
}
|
|
1393
|
-
|
|
1394
|
-
.playkit-code-inputs {
|
|
1395
|
-
gap: 6px;
|
|
1396
|
-
}
|
|
1397
|
-
}
|
|
1139
|
+
style.textContent = `
|
|
1140
|
+
.playkit-auth-modal {
|
|
1141
|
+
position: fixed;
|
|
1142
|
+
top: 0;
|
|
1143
|
+
left: 0;
|
|
1144
|
+
right: 0;
|
|
1145
|
+
bottom: 0;
|
|
1146
|
+
z-index: 999999;
|
|
1147
|
+
display: flex;
|
|
1148
|
+
justify-content: center;
|
|
1149
|
+
align-items: center;
|
|
1150
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
.playkit-auth-overlay {
|
|
1154
|
+
position: absolute;
|
|
1155
|
+
top: 0;
|
|
1156
|
+
left: 0;
|
|
1157
|
+
right: 0;
|
|
1158
|
+
bottom: 0;
|
|
1159
|
+
background: rgba(0, 0, 0, 0.8);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
.playkit-auth-container {
|
|
1163
|
+
position: relative;
|
|
1164
|
+
background: #fff;
|
|
1165
|
+
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
1166
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
|
|
1167
|
+
width: 90%;
|
|
1168
|
+
max-width: 320px;
|
|
1169
|
+
overflow: hidden;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
.playkit-auth-panel {
|
|
1173
|
+
padding: 24px;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
.playkit-auth-header {
|
|
1177
|
+
text-align: center;
|
|
1178
|
+
margin-bottom: 20px;
|
|
1179
|
+
position: relative;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
.playkit-auth-header h2 {
|
|
1183
|
+
margin: 0 0 8px 0;
|
|
1184
|
+
font-size: 14px;
|
|
1185
|
+
font-weight: 600;
|
|
1186
|
+
color: #171717;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
.playkit-auth-header p {
|
|
1190
|
+
margin: 0;
|
|
1191
|
+
font-size: 14px;
|
|
1192
|
+
color: #666;
|
|
1193
|
+
line-height: 1.5;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
.playkit-back-button {
|
|
1197
|
+
position: absolute;
|
|
1198
|
+
left: 0;
|
|
1199
|
+
top: 0;
|
|
1200
|
+
background: transparent;
|
|
1201
|
+
border: none;
|
|
1202
|
+
cursor: pointer;
|
|
1203
|
+
padding: 4px;
|
|
1204
|
+
color: #666;
|
|
1205
|
+
transition: background-color 0.2s ease, color 0.2s ease;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
.playkit-back-button:hover {
|
|
1209
|
+
background: #f5f5f5;
|
|
1210
|
+
color: #171717;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
.playkit-auth-toggle {
|
|
1214
|
+
display: flex;
|
|
1215
|
+
background: #f5f5f5;
|
|
1216
|
+
padding: 2px;
|
|
1217
|
+
margin-bottom: 20px;
|
|
1218
|
+
gap: 2px;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
.playkit-toggle-option {
|
|
1222
|
+
flex: 1;
|
|
1223
|
+
display: flex;
|
|
1224
|
+
justify-content: center;
|
|
1225
|
+
align-items: center;
|
|
1226
|
+
padding: 10px 16px;
|
|
1227
|
+
cursor: pointer;
|
|
1228
|
+
transition: background-color 0.2s ease;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
.playkit-toggle-option input {
|
|
1232
|
+
display: none;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
.playkit-toggle-option span {
|
|
1236
|
+
font-size: 14px;
|
|
1237
|
+
font-weight: 500;
|
|
1238
|
+
color: #666;
|
|
1239
|
+
transition: color 0.2s ease;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
.playkit-toggle-option input:checked + span {
|
|
1243
|
+
color: #fff;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
.playkit-toggle-option:has(input:checked) {
|
|
1247
|
+
background: #171717;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
.playkit-auth-input-group {
|
|
1251
|
+
margin-bottom: 20px;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
.playkit-input-wrapper {
|
|
1255
|
+
position: relative;
|
|
1256
|
+
display: flex;
|
|
1257
|
+
align-items: center;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
.playkit-input-icon {
|
|
1261
|
+
position: absolute;
|
|
1262
|
+
left: 12px;
|
|
1263
|
+
color: #999;
|
|
1264
|
+
pointer-events: none;
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
.playkit-input-wrapper input {
|
|
1268
|
+
width: 100%;
|
|
1269
|
+
padding: 10px 12px 10px 44px;
|
|
1270
|
+
border: 1px solid #e5e7eb;
|
|
1271
|
+
font-size: 14px;
|
|
1272
|
+
transition: border-color 0.2s ease;
|
|
1273
|
+
box-sizing: border-box;
|
|
1274
|
+
background: #fff;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
.playkit-input-wrapper input:hover {
|
|
1278
|
+
border-color: #d4d4d4;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
.playkit-input-wrapper input:focus {
|
|
1282
|
+
outline: none;
|
|
1283
|
+
border-color: #171717;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
.playkit-code-inputs {
|
|
1287
|
+
display: flex;
|
|
1288
|
+
gap: 8px;
|
|
1289
|
+
justify-content: center;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
.playkit-code-input {
|
|
1293
|
+
width: 40px !important;
|
|
1294
|
+
height: 48px;
|
|
1295
|
+
text-align: center;
|
|
1296
|
+
font-size: 20px;
|
|
1297
|
+
font-weight: 600;
|
|
1298
|
+
border: 1px solid #e5e7eb !important;
|
|
1299
|
+
padding: 0 !important;
|
|
1300
|
+
transition: border-color 0.2s ease;
|
|
1301
|
+
background: #fff;
|
|
1302
|
+
-moz-appearance: textfield;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
.playkit-code-input::-webkit-outer-spin-button,
|
|
1306
|
+
.playkit-code-input::-webkit-inner-spin-button {
|
|
1307
|
+
-webkit-appearance: none;
|
|
1308
|
+
margin: 0;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
.playkit-code-input:hover {
|
|
1312
|
+
border-color: #d4d4d4 !important;
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
.playkit-code-input:focus {
|
|
1316
|
+
outline: none;
|
|
1317
|
+
border-color: #171717 !important;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
.playkit-auth-button {
|
|
1321
|
+
width: 100%;
|
|
1322
|
+
padding: 10px 16px;
|
|
1323
|
+
background: #171717;
|
|
1324
|
+
color: white;
|
|
1325
|
+
border: none;
|
|
1326
|
+
font-size: 14px;
|
|
1327
|
+
font-weight: 500;
|
|
1328
|
+
cursor: pointer;
|
|
1329
|
+
transition: background 0.2s ease;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
.playkit-auth-button:hover:not(:disabled) {
|
|
1333
|
+
background: #404040;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
.playkit-auth-button:active:not(:disabled) {
|
|
1337
|
+
background: #0a0a0a;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
.playkit-auth-button:disabled {
|
|
1341
|
+
background: #e5e7eb;
|
|
1342
|
+
color: #999;
|
|
1343
|
+
cursor: not-allowed;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
.playkit-auth-error {
|
|
1347
|
+
margin-top: 16px;
|
|
1348
|
+
padding: 12px 16px;
|
|
1349
|
+
background: #fef2f2;
|
|
1350
|
+
border: 1px solid #fecaca;
|
|
1351
|
+
color: #dc2626;
|
|
1352
|
+
font-size: 13px;
|
|
1353
|
+
text-align: left;
|
|
1354
|
+
display: none;
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
.playkit-auth-error.show {
|
|
1358
|
+
display: block;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
.playkit-loading-overlay {
|
|
1362
|
+
position: absolute;
|
|
1363
|
+
top: 0;
|
|
1364
|
+
left: 0;
|
|
1365
|
+
right: 0;
|
|
1366
|
+
bottom: 0;
|
|
1367
|
+
background: rgba(255, 255, 255, 0.96);
|
|
1368
|
+
display: flex;
|
|
1369
|
+
justify-content: center;
|
|
1370
|
+
align-items: center;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
.playkit-spinner {
|
|
1374
|
+
width: 24px;
|
|
1375
|
+
height: 24px;
|
|
1376
|
+
border: 2px solid #e5e7eb;
|
|
1377
|
+
border-top: 2px solid #171717;
|
|
1378
|
+
border-radius: 50%;
|
|
1379
|
+
animation: playkit-spin 1s linear infinite;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
@keyframes playkit-spin {
|
|
1383
|
+
0% { transform: rotate(0deg); }
|
|
1384
|
+
100% { transform: rotate(360deg); }
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
@media (max-width: 480px) {
|
|
1388
|
+
.playkit-auth-container {
|
|
1389
|
+
width: 95%;
|
|
1390
|
+
max-width: none;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
.playkit-auth-panel {
|
|
1394
|
+
padding: 20px;
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
.playkit-code-input {
|
|
1398
|
+
width: 36px !important;
|
|
1399
|
+
height: 44px;
|
|
1400
|
+
font-size: 18px;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
.playkit-code-inputs {
|
|
1404
|
+
gap: 6px;
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1398
1407
|
`;
|
|
1399
1408
|
document.head.appendChild(style);
|
|
1400
1409
|
}
|
|
@@ -1415,14 +1424,14 @@ class AuthFlowManager extends EventEmitter {
|
|
|
1415
1424
|
: this.t('phonePlaceholder');
|
|
1416
1425
|
// Update icon
|
|
1417
1426
|
if (isEmail) {
|
|
1418
|
-
identifierIcon.innerHTML = `
|
|
1419
|
-
<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>
|
|
1420
|
-
<polyline points="22,6 12,13 2,6"></polyline>
|
|
1427
|
+
identifierIcon.innerHTML = `
|
|
1428
|
+
<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>
|
|
1429
|
+
<polyline points="22,6 12,13 2,6"></polyline>
|
|
1421
1430
|
`;
|
|
1422
1431
|
}
|
|
1423
1432
|
else {
|
|
1424
|
-
identifierIcon.innerHTML = `
|
|
1425
|
-
<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>
|
|
1433
|
+
identifierIcon.innerHTML = `
|
|
1434
|
+
<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>
|
|
1426
1435
|
`;
|
|
1427
1436
|
}
|
|
1428
1437
|
};
|
|
@@ -1443,7 +1452,7 @@ class AuthFlowManager extends EventEmitter {
|
|
|
1443
1452
|
this.otpInstance = new window.VanillaOTP(codeInputsContainer);
|
|
1444
1453
|
// Auto-submit when all 6 digits entered
|
|
1445
1454
|
const codeInputs = (_d = this.modal) === null || _d === void 0 ? void 0 : _d.querySelectorAll('.playkit-code-input');
|
|
1446
|
-
codeInputs === null || codeInputs === void 0 ? void 0 : codeInputs.forEach((input,
|
|
1455
|
+
codeInputs === null || codeInputs === void 0 ? void 0 : codeInputs.forEach((input, _index) => {
|
|
1447
1456
|
input.addEventListener('input', () => {
|
|
1448
1457
|
// Check if all inputs are filled
|
|
1449
1458
|
const allFilled = Array.from(codeInputs).every(inp => inp.value.length === 1);
|
|
@@ -1535,7 +1544,7 @@ class AuthFlowManager extends EventEmitter {
|
|
|
1535
1544
|
async sendVerificationCode(identifier, type) {
|
|
1536
1545
|
const response = await fetch(`${this.baseURL}/api/auth/send-code`, {
|
|
1537
1546
|
method: 'POST',
|
|
1538
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1547
|
+
headers: Object.assign({ 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
1539
1548
|
body: JSON.stringify({ identifier, type }),
|
|
1540
1549
|
});
|
|
1541
1550
|
if (!response.ok) {
|
|
@@ -1557,7 +1566,7 @@ class AuthFlowManager extends EventEmitter {
|
|
|
1557
1566
|
}
|
|
1558
1567
|
const response = await fetch(`${this.baseURL}/api/auth/verify-code`, {
|
|
1559
1568
|
method: 'POST',
|
|
1560
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1569
|
+
headers: Object.assign({ 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
1561
1570
|
body: JSON.stringify({
|
|
1562
1571
|
sessionId: this.currentSessionId,
|
|
1563
1572
|
code,
|
|
@@ -1578,7 +1587,9 @@ class AuthFlowManager extends EventEmitter {
|
|
|
1578
1587
|
async setDefaultAuthTypeByRegion() {
|
|
1579
1588
|
var _a;
|
|
1580
1589
|
try {
|
|
1581
|
-
const response = await fetch(`${this.baseURL}/api/reachability
|
|
1590
|
+
const response = await fetch(`${this.baseURL}/api/reachability`, {
|
|
1591
|
+
headers: Object.assign({}, getSDKHeaders()),
|
|
1592
|
+
});
|
|
1582
1593
|
if (response.ok) {
|
|
1583
1594
|
const data = await response.json();
|
|
1584
1595
|
if (data.region === 'CN') {
|
|
@@ -1835,76 +1846,76 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
1835
1846
|
// Create modal overlay - dark bg-black/80 style
|
|
1836
1847
|
const overlay = document.createElement('div');
|
|
1837
1848
|
overlay.id = 'playkit-login-modal';
|
|
1838
|
-
overlay.style.cssText = `
|
|
1839
|
-
position: fixed;
|
|
1840
|
-
top: 0;
|
|
1841
|
-
left: 0;
|
|
1842
|
-
right: 0;
|
|
1843
|
-
bottom: 0;
|
|
1844
|
-
background: rgba(0, 0, 0, 0.8);
|
|
1845
|
-
display: flex;
|
|
1846
|
-
align-items: center;
|
|
1847
|
-
justify-content: center;
|
|
1848
|
-
z-index: 999999;
|
|
1849
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
1849
|
+
overlay.style.cssText = `
|
|
1850
|
+
position: fixed;
|
|
1851
|
+
top: 0;
|
|
1852
|
+
left: 0;
|
|
1853
|
+
right: 0;
|
|
1854
|
+
bottom: 0;
|
|
1855
|
+
background: rgba(0, 0, 0, 0.8);
|
|
1856
|
+
display: flex;
|
|
1857
|
+
align-items: center;
|
|
1858
|
+
justify-content: center;
|
|
1859
|
+
z-index: 999999;
|
|
1860
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
1850
1861
|
`;
|
|
1851
1862
|
// Create modal card - square corners, shadow-xl style
|
|
1852
1863
|
const card = document.createElement('div');
|
|
1853
|
-
card.style.cssText = `
|
|
1854
|
-
background: #fff;
|
|
1855
|
-
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
1856
|
-
padding: 24px;
|
|
1857
|
-
max-width: 320px;
|
|
1858
|
-
width: 90%;
|
|
1859
|
-
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
|
|
1860
|
-
text-align: center;
|
|
1864
|
+
card.style.cssText = `
|
|
1865
|
+
background: #fff;
|
|
1866
|
+
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
1867
|
+
padding: 24px;
|
|
1868
|
+
max-width: 320px;
|
|
1869
|
+
width: 90%;
|
|
1870
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
|
|
1871
|
+
text-align: center;
|
|
1861
1872
|
`;
|
|
1862
1873
|
// Subtitle / status text
|
|
1863
1874
|
const subtitle = document.createElement('p');
|
|
1864
1875
|
subtitle.id = 'playkit-modal-subtitle';
|
|
1865
1876
|
subtitle.textContent = this.t('loginWithPlayKit');
|
|
1866
|
-
subtitle.style.cssText = `
|
|
1867
|
-
margin: 0 0 20px;
|
|
1868
|
-
font-size: 14px;
|
|
1869
|
-
color: #666;
|
|
1877
|
+
subtitle.style.cssText = `
|
|
1878
|
+
margin: 0 0 20px;
|
|
1879
|
+
font-size: 14px;
|
|
1880
|
+
color: #666;
|
|
1870
1881
|
`;
|
|
1871
1882
|
card.appendChild(subtitle);
|
|
1872
1883
|
// Loading spinner (hidden initially)
|
|
1873
1884
|
const spinner = document.createElement('div');
|
|
1874
1885
|
spinner.id = 'playkit-modal-spinner';
|
|
1875
|
-
spinner.style.cssText = `
|
|
1876
|
-
display: none;
|
|
1877
|
-
width: 24px;
|
|
1878
|
-
height: 24px;
|
|
1879
|
-
margin: 0 auto 16px;
|
|
1880
|
-
border: 2px solid #e5e7eb;
|
|
1881
|
-
border-top-color: #171717;
|
|
1882
|
-
border-radius: 50%;
|
|
1883
|
-
animation: playkit-spin 1s linear infinite;
|
|
1886
|
+
spinner.style.cssText = `
|
|
1887
|
+
display: none;
|
|
1888
|
+
width: 24px;
|
|
1889
|
+
height: 24px;
|
|
1890
|
+
margin: 0 auto 16px;
|
|
1891
|
+
border: 2px solid #e5e7eb;
|
|
1892
|
+
border-top-color: #171717;
|
|
1893
|
+
border-radius: 50%;
|
|
1894
|
+
animation: playkit-spin 1s linear infinite;
|
|
1884
1895
|
`;
|
|
1885
1896
|
card.appendChild(spinner);
|
|
1886
1897
|
// Add keyframes for spinner
|
|
1887
1898
|
const style = document.createElement('style');
|
|
1888
|
-
style.textContent = `
|
|
1889
|
-
@keyframes playkit-spin {
|
|
1890
|
-
to { transform: rotate(360deg); }
|
|
1891
|
-
}
|
|
1899
|
+
style.textContent = `
|
|
1900
|
+
@keyframes playkit-spin {
|
|
1901
|
+
to { transform: rotate(360deg); }
|
|
1902
|
+
}
|
|
1892
1903
|
`;
|
|
1893
1904
|
document.head.appendChild(style);
|
|
1894
1905
|
// Login button - square corners, simple dark style
|
|
1895
1906
|
const loginBtn = document.createElement('button');
|
|
1896
1907
|
loginBtn.id = 'playkit-modal-login-btn';
|
|
1897
1908
|
loginBtn.textContent = this.t('loginToPlay');
|
|
1898
|
-
loginBtn.style.cssText = `
|
|
1899
|
-
width: 100%;
|
|
1900
|
-
padding: 10px 16px;
|
|
1901
|
-
font-size: 14px;
|
|
1902
|
-
font-weight: 500;
|
|
1903
|
-
color: white;
|
|
1904
|
-
background: #171717;
|
|
1905
|
-
border: none;
|
|
1906
|
-
cursor: pointer;
|
|
1907
|
-
transition: background 0.2s ease;
|
|
1909
|
+
loginBtn.style.cssText = `
|
|
1910
|
+
width: 100%;
|
|
1911
|
+
padding: 10px 16px;
|
|
1912
|
+
font-size: 14px;
|
|
1913
|
+
font-weight: 500;
|
|
1914
|
+
color: white;
|
|
1915
|
+
background: #171717;
|
|
1916
|
+
border: none;
|
|
1917
|
+
cursor: pointer;
|
|
1918
|
+
transition: background 0.2s ease;
|
|
1908
1919
|
`;
|
|
1909
1920
|
loginBtn.onmouseenter = () => {
|
|
1910
1921
|
loginBtn.style.background = '#404040';
|
|
@@ -1931,18 +1942,18 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
1931
1942
|
const cancelBtn = document.createElement('button');
|
|
1932
1943
|
cancelBtn.id = 'playkit-modal-cancel-btn';
|
|
1933
1944
|
cancelBtn.textContent = this.t('cancel');
|
|
1934
|
-
cancelBtn.style.cssText = `
|
|
1935
|
-
display: none;
|
|
1936
|
-
width: 100%;
|
|
1937
|
-
margin-top: 8px;
|
|
1938
|
-
padding: 10px 16px;
|
|
1939
|
-
font-size: 14px;
|
|
1940
|
-
font-weight: 500;
|
|
1941
|
-
color: #666;
|
|
1942
|
-
background: transparent;
|
|
1943
|
-
border: 1px solid #e5e7eb;
|
|
1944
|
-
cursor: pointer;
|
|
1945
|
-
transition: all 0.2s ease;
|
|
1945
|
+
cancelBtn.style.cssText = `
|
|
1946
|
+
display: none;
|
|
1947
|
+
width: 100%;
|
|
1948
|
+
margin-top: 8px;
|
|
1949
|
+
padding: 10px 16px;
|
|
1950
|
+
font-size: 14px;
|
|
1951
|
+
font-weight: 500;
|
|
1952
|
+
color: #666;
|
|
1953
|
+
background: transparent;
|
|
1954
|
+
border: 1px solid #e5e7eb;
|
|
1955
|
+
cursor: pointer;
|
|
1956
|
+
transition: all 0.2s ease;
|
|
1946
1957
|
`;
|
|
1947
1958
|
cancelBtn.onmouseenter = () => {
|
|
1948
1959
|
cancelBtn.style.background = '#f5f5f5';
|
|
@@ -2012,11 +2023,11 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
2012
2023
|
// Create error title
|
|
2013
2024
|
const errorTitle = document.createElement('h3');
|
|
2014
2025
|
errorTitle.textContent = this.t(titleKey);
|
|
2015
|
-
errorTitle.style.cssText = `
|
|
2016
|
-
margin: 0 0 8px;
|
|
2017
|
-
font-size: 14px;
|
|
2018
|
-
font-weight: 600;
|
|
2019
|
-
color: ${iconColor};
|
|
2026
|
+
errorTitle.style.cssText = `
|
|
2027
|
+
margin: 0 0 8px;
|
|
2028
|
+
font-size: 14px;
|
|
2029
|
+
font-weight: 600;
|
|
2030
|
+
color: ${iconColor};
|
|
2020
2031
|
`;
|
|
2021
2032
|
// Update subtitle with error description
|
|
2022
2033
|
subtitle.textContent = this.t(descKey);
|
|
@@ -2086,9 +2097,7 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
2086
2097
|
// Step 1: Initiate device auth session
|
|
2087
2098
|
const initResponse = await fetch(`${this.baseURL}/api/device-auth/initiate`, {
|
|
2088
2099
|
method: 'POST',
|
|
2089
|
-
headers: {
|
|
2090
|
-
'Content-Type': 'application/json',
|
|
2091
|
-
},
|
|
2100
|
+
headers: Object.assign({ 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
2092
2101
|
body: JSON.stringify({
|
|
2093
2102
|
game_id: this.gameId,
|
|
2094
2103
|
code_challenge: codeChallenge,
|
|
@@ -2157,7 +2166,7 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
2157
2166
|
return;
|
|
2158
2167
|
}
|
|
2159
2168
|
try {
|
|
2160
|
-
const pollResponse = await fetch(`${this.baseURL}/api/device-auth/poll?session_id=${encodeURIComponent(session_id)}&code_verifier=${encodeURIComponent(codeVerifier)}
|
|
2169
|
+
const pollResponse = await fetch(`${this.baseURL}/api/device-auth/poll?session_id=${encodeURIComponent(session_id)}&code_verifier=${encodeURIComponent(codeVerifier)}`, { headers: Object.assign({}, getSDKHeaders()) });
|
|
2161
2170
|
const pollData = await pollResponse.json();
|
|
2162
2171
|
if (pollResponse.ok) {
|
|
2163
2172
|
if (pollData.status === 'pending') {
|
|
@@ -2285,9 +2294,7 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
2285
2294
|
// Initiate device auth session
|
|
2286
2295
|
const initResponse = await fetch(`${this.baseURL}/api/device-auth/initiate`, {
|
|
2287
2296
|
method: 'POST',
|
|
2288
|
-
headers: {
|
|
2289
|
-
'Content-Type': 'application/json',
|
|
2290
|
-
},
|
|
2297
|
+
headers: Object.assign({ 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
2291
2298
|
body: JSON.stringify({
|
|
2292
2299
|
game_id: this.gameId,
|
|
2293
2300
|
code_challenge: codeChallenge,
|
|
@@ -2350,7 +2357,7 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
2350
2357
|
return;
|
|
2351
2358
|
}
|
|
2352
2359
|
try {
|
|
2353
|
-
const pollResponse = await fetch(`${this.baseURL}/api/device-auth/poll?session_id=${encodeURIComponent(sessionId)}&code_verifier=${encodeURIComponent(codeVerifier)}
|
|
2360
|
+
const pollResponse = await fetch(`${this.baseURL}/api/device-auth/poll?session_id=${encodeURIComponent(sessionId)}&code_verifier=${encodeURIComponent(codeVerifier)}`, { headers: Object.assign({}, getSDKHeaders()) });
|
|
2354
2361
|
const pollData = await pollResponse.json();
|
|
2355
2362
|
if (pollResponse.ok) {
|
|
2356
2363
|
if (pollData.status === 'pending') {
|
|
@@ -2430,6 +2437,8 @@ class AuthManager extends EventEmitter {
|
|
|
2430
2437
|
this.logger = Logger.getLogger('AuthManager');
|
|
2431
2438
|
/** Shared promise for current device auth flow - allows multiple callers to await the same result */
|
|
2432
2439
|
this.currentDeviceAuthFlowPromise = null;
|
|
2440
|
+
/** Shared promise for current auth flow (startAuthFlow) - allows multiple callers to await the same result */
|
|
2441
|
+
this.currentAuthFlowPromise = null;
|
|
2433
2442
|
this.config = config;
|
|
2434
2443
|
// Create TokenStorage with appropriate mode for server vs browser environment
|
|
2435
2444
|
this.storage = new TokenStorage({
|
|
@@ -2538,12 +2547,27 @@ class AuthManager extends EventEmitter {
|
|
|
2538
2547
|
* @deprecated 'headless' authentication is deprecated and will be removed in v2.0. Use 'device' instead.
|
|
2539
2548
|
*/
|
|
2540
2549
|
async startAuthFlow(authMethod = 'device') {
|
|
2541
|
-
|
|
2542
|
-
if (this.
|
|
2543
|
-
|
|
2544
|
-
this.
|
|
2545
|
-
|
|
2550
|
+
// If a flow is already in progress, return the shared promise so all callers await the same result
|
|
2551
|
+
if (this.currentAuthFlowPromise) {
|
|
2552
|
+
this.logger.debug('Auth flow already in progress, waiting for existing flow');
|
|
2553
|
+
return this.currentAuthFlowPromise;
|
|
2554
|
+
}
|
|
2555
|
+
// Store the flow promise so subsequent calls can await the same result
|
|
2556
|
+
const flowPromise = this.executeAuthFlow(authMethod);
|
|
2557
|
+
this.currentAuthFlowPromise = flowPromise;
|
|
2558
|
+
try {
|
|
2559
|
+
return await flowPromise;
|
|
2560
|
+
}
|
|
2561
|
+
finally {
|
|
2562
|
+
this.currentAuthFlowPromise = null;
|
|
2546
2563
|
}
|
|
2564
|
+
}
|
|
2565
|
+
/**
|
|
2566
|
+
* Internal method that executes the actual auth flow
|
|
2567
|
+
* @private
|
|
2568
|
+
*/
|
|
2569
|
+
async executeAuthFlow(authMethod = 'device') {
|
|
2570
|
+
var _a, _b;
|
|
2547
2571
|
// Deprecation warning for headless auth
|
|
2548
2572
|
if (authMethod === 'headless') {
|
|
2549
2573
|
this.logger.warn('"headless" authentication is deprecated and will be removed in v2.0. ' +
|
|
@@ -2602,10 +2626,7 @@ class AuthManager extends EventEmitter {
|
|
|
2602
2626
|
try {
|
|
2603
2627
|
const response = await fetch(`${this.baseURL}${JWT_EXCHANGE_ENDPOINT}`, {
|
|
2604
2628
|
method: 'POST',
|
|
2605
|
-
headers: {
|
|
2606
|
-
Authorization: `Bearer ${jwt}`,
|
|
2607
|
-
'Content-Type': 'application/json',
|
|
2608
|
-
},
|
|
2629
|
+
headers: Object.assign({ Authorization: `Bearer ${jwt}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
2609
2630
|
body: JSON.stringify({ gameId: this.config.gameId }),
|
|
2610
2631
|
});
|
|
2611
2632
|
if (!response.ok) {
|
|
@@ -2955,9 +2976,7 @@ class AuthManager extends EventEmitter {
|
|
|
2955
2976
|
this.logger.debug('Refreshing access token');
|
|
2956
2977
|
const response = await fetch(`${this.baseURL}${TOKEN_REFRESH_ENDPOINT}`, {
|
|
2957
2978
|
method: 'POST',
|
|
2958
|
-
headers: {
|
|
2959
|
-
'Content-Type': 'application/json',
|
|
2960
|
-
},
|
|
2979
|
+
headers: Object.assign({ 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
2961
2980
|
body: JSON.stringify({
|
|
2962
2981
|
refresh_token: this.authState.refreshToken,
|
|
2963
2982
|
}),
|
|
@@ -3060,7 +3079,7 @@ const translations = {
|
|
|
3060
3079
|
* RechargeManager handles the recharge modal UI and recharge window opening
|
|
3061
3080
|
*/
|
|
3062
3081
|
class RechargeManager extends EventEmitter {
|
|
3063
|
-
constructor(playerToken, rechargePortalUrl = 'https://playkit.ai/recharge', gameId) {
|
|
3082
|
+
constructor(playerToken, rechargePortalUrl = 'https://players.playkit.ai/recharge', gameId) {
|
|
3064
3083
|
super();
|
|
3065
3084
|
this.modalContainer = null;
|
|
3066
3085
|
this.styleElement = null;
|
|
@@ -3163,220 +3182,220 @@ class RechargeManager extends EventEmitter {
|
|
|
3163
3182
|
return;
|
|
3164
3183
|
}
|
|
3165
3184
|
this.styleElement = document.createElement('style');
|
|
3166
|
-
this.styleElement.textContent = `
|
|
3167
|
-
.playkit-recharge-overlay {
|
|
3168
|
-
position: fixed;
|
|
3169
|
-
top: 0;
|
|
3170
|
-
left: 0;
|
|
3171
|
-
right: 0;
|
|
3172
|
-
bottom: 0;
|
|
3173
|
-
background: rgba(0, 0, 0, 0.8);
|
|
3174
|
-
display: flex;
|
|
3175
|
-
justify-content: center;
|
|
3176
|
-
align-items: center;
|
|
3177
|
-
z-index: 999999;
|
|
3178
|
-
animation: playkit-recharge-fadeIn 0.2s ease-out;
|
|
3179
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3180
|
-
}
|
|
3181
|
-
|
|
3182
|
-
@keyframes playkit-recharge-fadeIn {
|
|
3183
|
-
from {
|
|
3184
|
-
opacity: 0;
|
|
3185
|
-
}
|
|
3186
|
-
to {
|
|
3187
|
-
opacity: 1;
|
|
3188
|
-
}
|
|
3189
|
-
}
|
|
3190
|
-
|
|
3191
|
-
.playkit-recharge-modal {
|
|
3192
|
-
background: #fff;
|
|
3193
|
-
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
3194
|
-
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
|
|
3195
|
-
padding: 24px;
|
|
3196
|
-
max-width: 320px;
|
|
3197
|
-
width: 90%;
|
|
3198
|
-
position: relative;
|
|
3199
|
-
text-align: center;
|
|
3200
|
-
}
|
|
3201
|
-
|
|
3202
|
-
.playkit-recharge-title {
|
|
3203
|
-
font-size: 14px;
|
|
3204
|
-
font-weight: 600;
|
|
3205
|
-
color: #171717;
|
|
3206
|
-
margin: 0 0 8px 0;
|
|
3207
|
-
text-align: center;
|
|
3208
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3209
|
-
}
|
|
3210
|
-
|
|
3211
|
-
.playkit-recharge-message {
|
|
3212
|
-
font-size: 14px;
|
|
3213
|
-
color: #666;
|
|
3214
|
-
margin: 0 0 20px 0;
|
|
3215
|
-
text-align: center;
|
|
3216
|
-
line-height: 1.5;
|
|
3217
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3218
|
-
}
|
|
3219
|
-
|
|
3220
|
-
.playkit-recharge-balance {
|
|
3221
|
-
background: #f5f5f5;
|
|
3222
|
-
border: 1px solid #e5e7eb;
|
|
3223
|
-
padding: 16px;
|
|
3224
|
-
margin: 0 0 20px 0;
|
|
3225
|
-
text-align: center;
|
|
3226
|
-
}
|
|
3227
|
-
|
|
3228
|
-
.playkit-recharge-balance-label {
|
|
3229
|
-
font-size: 12px;
|
|
3230
|
-
color: #666;
|
|
3231
|
-
margin: 0 0 8px 0;
|
|
3232
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3233
|
-
}
|
|
3234
|
-
|
|
3235
|
-
.playkit-recharge-balance-value {
|
|
3236
|
-
font-size: 24px;
|
|
3237
|
-
font-weight: bold;
|
|
3238
|
-
color: #171717;
|
|
3239
|
-
margin: 0;
|
|
3240
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3241
|
-
}
|
|
3242
|
-
|
|
3243
|
-
.playkit-recharge-balance-unit {
|
|
3244
|
-
font-size: 14px;
|
|
3245
|
-
color: #666;
|
|
3246
|
-
margin-left: 4px;
|
|
3247
|
-
}
|
|
3248
|
-
|
|
3249
|
-
.playkit-recharge-buttons {
|
|
3250
|
-
display: flex;
|
|
3251
|
-
flex-direction: column;
|
|
3252
|
-
gap: 8px;
|
|
3253
|
-
}
|
|
3254
|
-
|
|
3255
|
-
.playkit-recharge-button {
|
|
3256
|
-
width: 100%;
|
|
3257
|
-
padding: 10px 16px;
|
|
3258
|
-
border: none;
|
|
3259
|
-
font-size: 14px;
|
|
3260
|
-
font-weight: 500;
|
|
3261
|
-
cursor: pointer;
|
|
3262
|
-
transition: all 0.2s ease;
|
|
3263
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3264
|
-
}
|
|
3265
|
-
|
|
3266
|
-
.playkit-recharge-button-primary {
|
|
3267
|
-
background: #171717;
|
|
3268
|
-
color: white;
|
|
3269
|
-
}
|
|
3270
|
-
|
|
3271
|
-
.playkit-recharge-button-primary:hover {
|
|
3272
|
-
background: #404040;
|
|
3273
|
-
}
|
|
3274
|
-
|
|
3275
|
-
.playkit-recharge-button-primary:active {
|
|
3276
|
-
background: #0a0a0a;
|
|
3277
|
-
}
|
|
3278
|
-
|
|
3279
|
-
.playkit-recharge-button-secondary {
|
|
3280
|
-
background: transparent;
|
|
3281
|
-
color: #666;
|
|
3282
|
-
border: 1px solid #e5e7eb;
|
|
3283
|
-
}
|
|
3284
|
-
|
|
3285
|
-
.playkit-recharge-button-secondary:hover {
|
|
3286
|
-
background: #f5f5f5;
|
|
3287
|
-
border-color: #d4d4d4;
|
|
3288
|
-
}
|
|
3289
|
-
|
|
3290
|
-
.playkit-recharge-button-secondary:active {
|
|
3291
|
-
background: #e5e5e5;
|
|
3292
|
-
}
|
|
3293
|
-
|
|
3294
|
-
@media (max-width: 480px) {
|
|
3295
|
-
.playkit-recharge-modal {
|
|
3296
|
-
padding: 20px;
|
|
3297
|
-
}
|
|
3298
|
-
}
|
|
3299
|
-
|
|
3300
|
-
/* Daily Refresh Toast Styles */
|
|
3301
|
-
.playkit-daily-refresh-toast {
|
|
3302
|
-
position: fixed;
|
|
3303
|
-
top: 20px;
|
|
3304
|
-
right: 20px;
|
|
3305
|
-
background: #fff;
|
|
3306
|
-
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
3307
|
-
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.1);
|
|
3308
|
-
padding: 16px 20px;
|
|
3309
|
-
min-width: 240px;
|
|
3310
|
-
max-width: 320px;
|
|
3311
|
-
z-index: 999998;
|
|
3312
|
-
animation: playkit-toast-slideIn 0.3s ease-out;
|
|
3313
|
-
display: flex;
|
|
3314
|
-
align-items: flex-start;
|
|
3315
|
-
gap: 12px;
|
|
3316
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3317
|
-
}
|
|
3318
|
-
|
|
3319
|
-
.playkit-daily-refresh-toast.hiding {
|
|
3320
|
-
animation: playkit-toast-fadeOut 0.3s ease-out forwards;
|
|
3321
|
-
}
|
|
3322
|
-
|
|
3323
|
-
@keyframes playkit-toast-slideIn {
|
|
3324
|
-
from {
|
|
3325
|
-
transform: translateX(100%);
|
|
3326
|
-
opacity: 0;
|
|
3327
|
-
}
|
|
3328
|
-
to {
|
|
3329
|
-
transform: translateX(0);
|
|
3330
|
-
opacity: 1;
|
|
3331
|
-
}
|
|
3332
|
-
}
|
|
3333
|
-
|
|
3334
|
-
@keyframes playkit-toast-fadeOut {
|
|
3335
|
-
from {
|
|
3336
|
-
transform: translateX(0);
|
|
3337
|
-
opacity: 1;
|
|
3338
|
-
}
|
|
3339
|
-
to {
|
|
3340
|
-
transform: translateX(100%);
|
|
3341
|
-
opacity: 0;
|
|
3342
|
-
}
|
|
3343
|
-
}
|
|
3344
|
-
|
|
3345
|
-
.playkit-toast-icon {
|
|
3346
|
-
width: 24px;
|
|
3347
|
-
height: 24px;
|
|
3348
|
-
background: #171717;
|
|
3349
|
-
border-radius: 50%;
|
|
3350
|
-
display: flex;
|
|
3351
|
-
align-items: center;
|
|
3352
|
-
justify-content: center;
|
|
3353
|
-
flex-shrink: 0;
|
|
3354
|
-
}
|
|
3355
|
-
|
|
3356
|
-
.playkit-toast-icon svg {
|
|
3357
|
-
width: 14px;
|
|
3358
|
-
height: 14px;
|
|
3359
|
-
color: #ffffff;
|
|
3360
|
-
}
|
|
3361
|
-
|
|
3362
|
-
.playkit-toast-message {
|
|
3363
|
-
flex: 1;
|
|
3364
|
-
font-size: 14px;
|
|
3365
|
-
font-weight: 500;
|
|
3366
|
-
color: #171717;
|
|
3367
|
-
line-height: 1.4;
|
|
3368
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3369
|
-
}
|
|
3370
|
-
|
|
3371
|
-
@media (max-width: 480px) {
|
|
3372
|
-
.playkit-daily-refresh-toast {
|
|
3373
|
-
top: 10px;
|
|
3374
|
-
right: 10px;
|
|
3375
|
-
left: 10px;
|
|
3376
|
-
min-width: auto;
|
|
3377
|
-
max-width: none;
|
|
3378
|
-
}
|
|
3379
|
-
}
|
|
3185
|
+
this.styleElement.textContent = `
|
|
3186
|
+
.playkit-recharge-overlay {
|
|
3187
|
+
position: fixed;
|
|
3188
|
+
top: 0;
|
|
3189
|
+
left: 0;
|
|
3190
|
+
right: 0;
|
|
3191
|
+
bottom: 0;
|
|
3192
|
+
background: rgba(0, 0, 0, 0.8);
|
|
3193
|
+
display: flex;
|
|
3194
|
+
justify-content: center;
|
|
3195
|
+
align-items: center;
|
|
3196
|
+
z-index: 999999;
|
|
3197
|
+
animation: playkit-recharge-fadeIn 0.2s ease-out;
|
|
3198
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3199
|
+
}
|
|
3200
|
+
|
|
3201
|
+
@keyframes playkit-recharge-fadeIn {
|
|
3202
|
+
from {
|
|
3203
|
+
opacity: 0;
|
|
3204
|
+
}
|
|
3205
|
+
to {
|
|
3206
|
+
opacity: 1;
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
3209
|
+
|
|
3210
|
+
.playkit-recharge-modal {
|
|
3211
|
+
background: #fff;
|
|
3212
|
+
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
3213
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
|
|
3214
|
+
padding: 24px;
|
|
3215
|
+
max-width: 320px;
|
|
3216
|
+
width: 90%;
|
|
3217
|
+
position: relative;
|
|
3218
|
+
text-align: center;
|
|
3219
|
+
}
|
|
3220
|
+
|
|
3221
|
+
.playkit-recharge-title {
|
|
3222
|
+
font-size: 14px;
|
|
3223
|
+
font-weight: 600;
|
|
3224
|
+
color: #171717;
|
|
3225
|
+
margin: 0 0 8px 0;
|
|
3226
|
+
text-align: center;
|
|
3227
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3228
|
+
}
|
|
3229
|
+
|
|
3230
|
+
.playkit-recharge-message {
|
|
3231
|
+
font-size: 14px;
|
|
3232
|
+
color: #666;
|
|
3233
|
+
margin: 0 0 20px 0;
|
|
3234
|
+
text-align: center;
|
|
3235
|
+
line-height: 1.5;
|
|
3236
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3237
|
+
}
|
|
3238
|
+
|
|
3239
|
+
.playkit-recharge-balance {
|
|
3240
|
+
background: #f5f5f5;
|
|
3241
|
+
border: 1px solid #e5e7eb;
|
|
3242
|
+
padding: 16px;
|
|
3243
|
+
margin: 0 0 20px 0;
|
|
3244
|
+
text-align: center;
|
|
3245
|
+
}
|
|
3246
|
+
|
|
3247
|
+
.playkit-recharge-balance-label {
|
|
3248
|
+
font-size: 12px;
|
|
3249
|
+
color: #666;
|
|
3250
|
+
margin: 0 0 8px 0;
|
|
3251
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3252
|
+
}
|
|
3253
|
+
|
|
3254
|
+
.playkit-recharge-balance-value {
|
|
3255
|
+
font-size: 24px;
|
|
3256
|
+
font-weight: bold;
|
|
3257
|
+
color: #171717;
|
|
3258
|
+
margin: 0;
|
|
3259
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3260
|
+
}
|
|
3261
|
+
|
|
3262
|
+
.playkit-recharge-balance-unit {
|
|
3263
|
+
font-size: 14px;
|
|
3264
|
+
color: #666;
|
|
3265
|
+
margin-left: 4px;
|
|
3266
|
+
}
|
|
3267
|
+
|
|
3268
|
+
.playkit-recharge-buttons {
|
|
3269
|
+
display: flex;
|
|
3270
|
+
flex-direction: column;
|
|
3271
|
+
gap: 8px;
|
|
3272
|
+
}
|
|
3273
|
+
|
|
3274
|
+
.playkit-recharge-button {
|
|
3275
|
+
width: 100%;
|
|
3276
|
+
padding: 10px 16px;
|
|
3277
|
+
border: none;
|
|
3278
|
+
font-size: 14px;
|
|
3279
|
+
font-weight: 500;
|
|
3280
|
+
cursor: pointer;
|
|
3281
|
+
transition: all 0.2s ease;
|
|
3282
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3283
|
+
}
|
|
3284
|
+
|
|
3285
|
+
.playkit-recharge-button-primary {
|
|
3286
|
+
background: #171717;
|
|
3287
|
+
color: white;
|
|
3288
|
+
}
|
|
3289
|
+
|
|
3290
|
+
.playkit-recharge-button-primary:hover {
|
|
3291
|
+
background: #404040;
|
|
3292
|
+
}
|
|
3293
|
+
|
|
3294
|
+
.playkit-recharge-button-primary:active {
|
|
3295
|
+
background: #0a0a0a;
|
|
3296
|
+
}
|
|
3297
|
+
|
|
3298
|
+
.playkit-recharge-button-secondary {
|
|
3299
|
+
background: transparent;
|
|
3300
|
+
color: #666;
|
|
3301
|
+
border: 1px solid #e5e7eb;
|
|
3302
|
+
}
|
|
3303
|
+
|
|
3304
|
+
.playkit-recharge-button-secondary:hover {
|
|
3305
|
+
background: #f5f5f5;
|
|
3306
|
+
border-color: #d4d4d4;
|
|
3307
|
+
}
|
|
3308
|
+
|
|
3309
|
+
.playkit-recharge-button-secondary:active {
|
|
3310
|
+
background: #e5e5e5;
|
|
3311
|
+
}
|
|
3312
|
+
|
|
3313
|
+
@media (max-width: 480px) {
|
|
3314
|
+
.playkit-recharge-modal {
|
|
3315
|
+
padding: 20px;
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
|
|
3319
|
+
/* Daily Refresh Toast Styles */
|
|
3320
|
+
.playkit-daily-refresh-toast {
|
|
3321
|
+
position: fixed;
|
|
3322
|
+
top: 20px;
|
|
3323
|
+
right: 20px;
|
|
3324
|
+
background: #fff;
|
|
3325
|
+
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
3326
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.1);
|
|
3327
|
+
padding: 16px 20px;
|
|
3328
|
+
min-width: 240px;
|
|
3329
|
+
max-width: 320px;
|
|
3330
|
+
z-index: 999998;
|
|
3331
|
+
animation: playkit-toast-slideIn 0.3s ease-out;
|
|
3332
|
+
display: flex;
|
|
3333
|
+
align-items: flex-start;
|
|
3334
|
+
gap: 12px;
|
|
3335
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3336
|
+
}
|
|
3337
|
+
|
|
3338
|
+
.playkit-daily-refresh-toast.hiding {
|
|
3339
|
+
animation: playkit-toast-fadeOut 0.3s ease-out forwards;
|
|
3340
|
+
}
|
|
3341
|
+
|
|
3342
|
+
@keyframes playkit-toast-slideIn {
|
|
3343
|
+
from {
|
|
3344
|
+
transform: translateX(100%);
|
|
3345
|
+
opacity: 0;
|
|
3346
|
+
}
|
|
3347
|
+
to {
|
|
3348
|
+
transform: translateX(0);
|
|
3349
|
+
opacity: 1;
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
|
|
3353
|
+
@keyframes playkit-toast-fadeOut {
|
|
3354
|
+
from {
|
|
3355
|
+
transform: translateX(0);
|
|
3356
|
+
opacity: 1;
|
|
3357
|
+
}
|
|
3358
|
+
to {
|
|
3359
|
+
transform: translateX(100%);
|
|
3360
|
+
opacity: 0;
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
|
|
3364
|
+
.playkit-toast-icon {
|
|
3365
|
+
width: 24px;
|
|
3366
|
+
height: 24px;
|
|
3367
|
+
background: #171717;
|
|
3368
|
+
border-radius: 50%;
|
|
3369
|
+
display: flex;
|
|
3370
|
+
align-items: center;
|
|
3371
|
+
justify-content: center;
|
|
3372
|
+
flex-shrink: 0;
|
|
3373
|
+
}
|
|
3374
|
+
|
|
3375
|
+
.playkit-toast-icon svg {
|
|
3376
|
+
width: 14px;
|
|
3377
|
+
height: 14px;
|
|
3378
|
+
color: #ffffff;
|
|
3379
|
+
}
|
|
3380
|
+
|
|
3381
|
+
.playkit-toast-message {
|
|
3382
|
+
flex: 1;
|
|
3383
|
+
font-size: 14px;
|
|
3384
|
+
font-weight: 500;
|
|
3385
|
+
color: #171717;
|
|
3386
|
+
line-height: 1.4;
|
|
3387
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3388
|
+
}
|
|
3389
|
+
|
|
3390
|
+
@media (max-width: 480px) {
|
|
3391
|
+
.playkit-daily-refresh-toast {
|
|
3392
|
+
top: 10px;
|
|
3393
|
+
right: 10px;
|
|
3394
|
+
left: 10px;
|
|
3395
|
+
min-width: auto;
|
|
3396
|
+
max-width: none;
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3380
3399
|
`;
|
|
3381
3400
|
document.head.appendChild(this.styleElement);
|
|
3382
3401
|
}
|
|
@@ -3513,7 +3532,8 @@ class RechargeManager extends EventEmitter {
|
|
|
3513
3532
|
/**
|
|
3514
3533
|
* Player client for managing player information and credits
|
|
3515
3534
|
*/
|
|
3516
|
-
|
|
3535
|
+
// @ts-ignore - replaced at build time
|
|
3536
|
+
const DEFAULT_BASE_URL$4 = "https://api.playkit.ai";
|
|
3517
3537
|
const PLAYER_INFO_ENDPOINT = '/api/external/player-info';
|
|
3518
3538
|
const SET_NICKNAME_ENDPOINT = '/api/external/set-game-player-nickname';
|
|
3519
3539
|
class PlayerClient extends EventEmitter {
|
|
@@ -3531,7 +3551,7 @@ class PlayerClient extends EventEmitter {
|
|
|
3531
3551
|
autoShowBalanceModal: (_a = rechargeConfig.autoShowBalanceModal) !== null && _a !== void 0 ? _a : true,
|
|
3532
3552
|
balanceCheckInterval: (_b = rechargeConfig.balanceCheckInterval) !== null && _b !== void 0 ? _b : 30000,
|
|
3533
3553
|
checkBalanceAfterApiCall: (_c = rechargeConfig.checkBalanceAfterApiCall) !== null && _c !== void 0 ? _c : true,
|
|
3534
|
-
rechargePortalUrl: rechargeConfig.rechargePortalUrl || 'https://playkit.ai/recharge',
|
|
3554
|
+
rechargePortalUrl: rechargeConfig.rechargePortalUrl || 'https://players.playkit.ai/recharge',
|
|
3535
3555
|
showDailyRefreshToast: (_d = rechargeConfig.showDailyRefreshToast) !== null && _d !== void 0 ? _d : true,
|
|
3536
3556
|
};
|
|
3537
3557
|
}
|
|
@@ -3546,9 +3566,7 @@ class PlayerClient extends EventEmitter {
|
|
|
3546
3566
|
}
|
|
3547
3567
|
try {
|
|
3548
3568
|
// Build headers with X-Game-Id to support Global Developer Token
|
|
3549
|
-
const headers = {
|
|
3550
|
-
Authorization: `Bearer ${token}`,
|
|
3551
|
-
};
|
|
3569
|
+
const headers = Object.assign({ Authorization: `Bearer ${token}` }, getSDKHeaders());
|
|
3552
3570
|
if (this.gameId) {
|
|
3553
3571
|
headers['X-Game-Id'] = this.gameId;
|
|
3554
3572
|
}
|
|
@@ -3648,10 +3666,7 @@ class PlayerClient extends EventEmitter {
|
|
|
3648
3666
|
try {
|
|
3649
3667
|
const response = await fetch(`${this.baseURL}${SET_NICKNAME_ENDPOINT}`, {
|
|
3650
3668
|
method: 'POST',
|
|
3651
|
-
headers: {
|
|
3652
|
-
Authorization: `Bearer ${token}`,
|
|
3653
|
-
'Content-Type': 'application/json',
|
|
3654
|
-
},
|
|
3669
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
3655
3670
|
body: JSON.stringify({ nickname: trimmed }),
|
|
3656
3671
|
});
|
|
3657
3672
|
if (!response.ok) {
|
|
@@ -3801,10 +3816,84 @@ class PlayerClient extends EventEmitter {
|
|
|
3801
3816
|
}
|
|
3802
3817
|
}
|
|
3803
3818
|
|
|
3819
|
+
const VALID_PART_TYPES = new Set([
|
|
3820
|
+
'text',
|
|
3821
|
+
'image',
|
|
3822
|
+
'image_url',
|
|
3823
|
+
'file',
|
|
3824
|
+
'audio',
|
|
3825
|
+
'input_audio',
|
|
3826
|
+
]);
|
|
3827
|
+
function describePart(part) {
|
|
3828
|
+
if (part === null)
|
|
3829
|
+
return 'null';
|
|
3830
|
+
if (typeof part !== 'object')
|
|
3831
|
+
return typeof part;
|
|
3832
|
+
const keys = Object.keys(part).slice(0, 5).join(',');
|
|
3833
|
+
return `{${keys}}`;
|
|
3834
|
+
}
|
|
3835
|
+
/**
|
|
3836
|
+
* Validate that `messages` matches the SDK's `Message[]` runtime contract before
|
|
3837
|
+
* shipping to the chat API. Throws `PlayKitError('INVALID_MESSAGES')` when a
|
|
3838
|
+
* caller has wrapped a Message[] inside one user message's `content` (the
|
|
3839
|
+
* `[{role:'user', content: [{role,...}, ...]}]` anti-pattern that bypasses the
|
|
3840
|
+
* `MessageContentPart` type at runtime).
|
|
3841
|
+
*
|
|
3842
|
+
* Does NOT auto-flatten — silently guessing system/user roles would mask bugs.
|
|
3843
|
+
*/
|
|
3844
|
+
function assertValidMessages(messages) {
|
|
3845
|
+
if (!Array.isArray(messages)) {
|
|
3846
|
+
throw new PlayKitError('messages must be an array of Message', 'INVALID_MESSAGES');
|
|
3847
|
+
}
|
|
3848
|
+
for (let i = 0; i < messages.length; i++) {
|
|
3849
|
+
const msg = messages[i];
|
|
3850
|
+
if (!msg || typeof msg !== 'object') {
|
|
3851
|
+
throw new PlayKitError(`messages[${i}] must be an object with {role, content}`, 'INVALID_MESSAGES');
|
|
3852
|
+
}
|
|
3853
|
+
const content = msg.content;
|
|
3854
|
+
if (typeof content === 'string' || content == null)
|
|
3855
|
+
continue;
|
|
3856
|
+
if (!Array.isArray(content)) {
|
|
3857
|
+
throw new PlayKitError(`messages[${i}].content must be a string or an array of content parts (got ${typeof content})`, 'INVALID_MESSAGES');
|
|
3858
|
+
}
|
|
3859
|
+
for (let j = 0; j < content.length; j++) {
|
|
3860
|
+
const part = content[j];
|
|
3861
|
+
if (!part || typeof part !== 'object') {
|
|
3862
|
+
throw new PlayKitError(`messages[${i}].content[${j}] must be a content part object (got ${typeof part})`, 'INVALID_MESSAGES');
|
|
3863
|
+
}
|
|
3864
|
+
const hasType = typeof part.type === 'string' && VALID_PART_TYPES.has(part.type);
|
|
3865
|
+
if (!hasType) {
|
|
3866
|
+
if ('role' in part && 'content' in part) {
|
|
3867
|
+
throw new PlayKitError(`messages[${i}].content[${j}] is shaped like a Message (has role/content) ` +
|
|
3868
|
+
`but content parts must be {type:'text'|'image'|'image_url'|'file'|'audio'|'input_audio',...}. ` +
|
|
3869
|
+
`Did you mean to pass that array as messages directly? ` +
|
|
3870
|
+
`e.g. \`messages: theArray\` instead of \`messages: [{role:'user', content: theArray}]\`. ` +
|
|
3871
|
+
`Got part ${describePart(part)}`, 'INVALID_MESSAGES');
|
|
3872
|
+
}
|
|
3873
|
+
throw new PlayKitError(`messages[${i}].content[${j}] is missing a recognized 'type' field ` +
|
|
3874
|
+
`(expected one of text|image|image_url|file|audio|input_audio). Got part ${describePart(part)}`, 'INVALID_MESSAGES');
|
|
3875
|
+
}
|
|
3876
|
+
}
|
|
3877
|
+
}
|
|
3878
|
+
}
|
|
3879
|
+
|
|
3804
3880
|
/**
|
|
3805
3881
|
* Chat provider for HTTP communication with chat API
|
|
3806
3882
|
*/
|
|
3807
|
-
|
|
3883
|
+
/**
|
|
3884
|
+
* Helper to extract string from MessageContent
|
|
3885
|
+
*/
|
|
3886
|
+
function contentToString$1(content) {
|
|
3887
|
+
if (!content)
|
|
3888
|
+
return '';
|
|
3889
|
+
if (typeof content === 'string')
|
|
3890
|
+
return content;
|
|
3891
|
+
// For array of content parts, extract text parts
|
|
3892
|
+
const textParts = content.filter(part => part.type === 'text');
|
|
3893
|
+
return textParts.map(part => part.text).join('');
|
|
3894
|
+
}
|
|
3895
|
+
// @ts-ignore - replaced at build time
|
|
3896
|
+
const DEFAULT_BASE_URL$3 = "https://api.playkit.ai";
|
|
3808
3897
|
class ChatProvider {
|
|
3809
3898
|
constructor(authManager, config) {
|
|
3810
3899
|
this.authManager = authManager;
|
|
@@ -3822,6 +3911,7 @@ class ChatProvider {
|
|
|
3822
3911
|
*/
|
|
3823
3912
|
async chatCompletion(chatConfig) {
|
|
3824
3913
|
var _a;
|
|
3914
|
+
assertValidMessages(chatConfig.messages);
|
|
3825
3915
|
// Ensure token is valid, auto-refresh if needed (browser mode only)
|
|
3826
3916
|
await this.authManager.ensureValidToken();
|
|
3827
3917
|
const token = this.authManager.getToken();
|
|
@@ -3843,10 +3933,7 @@ class ChatProvider {
|
|
|
3843
3933
|
try {
|
|
3844
3934
|
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
3845
3935
|
method: 'POST',
|
|
3846
|
-
headers: {
|
|
3847
|
-
Authorization: `Bearer ${token}`,
|
|
3848
|
-
'Content-Type': 'application/json',
|
|
3849
|
-
},
|
|
3936
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
3850
3937
|
body: JSON.stringify(requestBody),
|
|
3851
3938
|
});
|
|
3852
3939
|
if (!response.ok) {
|
|
@@ -3881,6 +3968,7 @@ class ChatProvider {
|
|
|
3881
3968
|
*/
|
|
3882
3969
|
async chatCompletionStream(chatConfig) {
|
|
3883
3970
|
var _a;
|
|
3971
|
+
assertValidMessages(chatConfig.messages);
|
|
3884
3972
|
// Ensure token is valid, auto-refresh if needed (browser mode only)
|
|
3885
3973
|
await this.authManager.ensureValidToken();
|
|
3886
3974
|
const token = this.authManager.getToken();
|
|
@@ -3902,10 +3990,7 @@ class ChatProvider {
|
|
|
3902
3990
|
try {
|
|
3903
3991
|
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
3904
3992
|
method: 'POST',
|
|
3905
|
-
headers: {
|
|
3906
|
-
Authorization: `Bearer ${token}`,
|
|
3907
|
-
'Content-Type': 'application/json',
|
|
3908
|
-
},
|
|
3993
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
3909
3994
|
body: JSON.stringify(requestBody),
|
|
3910
3995
|
});
|
|
3911
3996
|
if (!response.ok) {
|
|
@@ -3942,6 +4027,7 @@ class ChatProvider {
|
|
|
3942
4027
|
*/
|
|
3943
4028
|
async chatCompletionWithTools(chatConfig) {
|
|
3944
4029
|
var _a, _b;
|
|
4030
|
+
assertValidMessages(chatConfig.messages);
|
|
3945
4031
|
const token = this.authManager.getToken();
|
|
3946
4032
|
if (!token) {
|
|
3947
4033
|
throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
|
|
@@ -3968,10 +4054,7 @@ class ChatProvider {
|
|
|
3968
4054
|
try {
|
|
3969
4055
|
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
3970
4056
|
method: 'POST',
|
|
3971
|
-
headers: {
|
|
3972
|
-
Authorization: `Bearer ${token}`,
|
|
3973
|
-
'Content-Type': 'application/json',
|
|
3974
|
-
},
|
|
4057
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
3975
4058
|
body: JSON.stringify(requestBody),
|
|
3976
4059
|
});
|
|
3977
4060
|
if (!response.ok) {
|
|
@@ -4002,6 +4085,7 @@ class ChatProvider {
|
|
|
4002
4085
|
*/
|
|
4003
4086
|
async chatCompletionWithToolsStream(chatConfig) {
|
|
4004
4087
|
var _a, _b;
|
|
4088
|
+
assertValidMessages(chatConfig.messages);
|
|
4005
4089
|
const token = this.authManager.getToken();
|
|
4006
4090
|
if (!token) {
|
|
4007
4091
|
throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
|
|
@@ -4028,10 +4112,7 @@ class ChatProvider {
|
|
|
4028
4112
|
try {
|
|
4029
4113
|
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
4030
4114
|
method: 'POST',
|
|
4031
|
-
headers: {
|
|
4032
|
-
Authorization: `Bearer ${token}`,
|
|
4033
|
-
'Content-Type': 'application/json',
|
|
4034
|
-
},
|
|
4115
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
4035
4116
|
body: JSON.stringify(requestBody),
|
|
4036
4117
|
});
|
|
4037
4118
|
if (!response.ok) {
|
|
@@ -4101,10 +4182,7 @@ class ChatProvider {
|
|
|
4101
4182
|
try {
|
|
4102
4183
|
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
4103
4184
|
method: 'POST',
|
|
4104
|
-
headers: {
|
|
4105
|
-
Authorization: `Bearer ${token}`,
|
|
4106
|
-
'Content-Type': 'application/json',
|
|
4107
|
-
},
|
|
4185
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
4108
4186
|
body: JSON.stringify(requestBody),
|
|
4109
4187
|
});
|
|
4110
4188
|
if (!response.ok) {
|
|
@@ -4122,11 +4200,12 @@ class ChatProvider {
|
|
|
4122
4200
|
this.playerClient.checkBalanceAfterApiCall().catch(() => { });
|
|
4123
4201
|
}
|
|
4124
4202
|
// Parse the response content as JSON
|
|
4125
|
-
const
|
|
4126
|
-
if (!
|
|
4203
|
+
const rawContent = (_a = result.choices[0]) === null || _a === void 0 ? void 0 : _a.message.content;
|
|
4204
|
+
if (!rawContent) {
|
|
4127
4205
|
throw new PlayKitError('No content in response', 'NO_CONTENT');
|
|
4128
4206
|
}
|
|
4129
4207
|
try {
|
|
4208
|
+
const content = contentToString$1(rawContent);
|
|
4130
4209
|
return JSON.parse(content);
|
|
4131
4210
|
}
|
|
4132
4211
|
catch (parseError) {
|
|
@@ -4145,7 +4224,8 @@ class ChatProvider {
|
|
|
4145
4224
|
/**
|
|
4146
4225
|
* Image generation provider for HTTP communication with image API
|
|
4147
4226
|
*/
|
|
4148
|
-
|
|
4227
|
+
// @ts-ignore - replaced at build time
|
|
4228
|
+
const DEFAULT_BASE_URL$2 = "https://api.playkit.ai";
|
|
4149
4229
|
class ImageProvider {
|
|
4150
4230
|
constructor(authManager, config) {
|
|
4151
4231
|
this.authManager = authManager;
|
|
@@ -4198,10 +4278,7 @@ class ImageProvider {
|
|
|
4198
4278
|
try {
|
|
4199
4279
|
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
4200
4280
|
method: 'POST',
|
|
4201
|
-
headers: {
|
|
4202
|
-
Authorization: `Bearer ${token}`,
|
|
4203
|
-
'Content-Type': 'application/json',
|
|
4204
|
-
},
|
|
4281
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
4205
4282
|
body: JSON.stringify(requestBody),
|
|
4206
4283
|
});
|
|
4207
4284
|
if (!response.ok) {
|
|
@@ -4236,7 +4313,8 @@ class ImageProvider {
|
|
|
4236
4313
|
/**
|
|
4237
4314
|
* Transcription provider for HTTP communication with audio transcription API
|
|
4238
4315
|
*/
|
|
4239
|
-
|
|
4316
|
+
// @ts-ignore - replaced at build time
|
|
4317
|
+
const DEFAULT_BASE_URL$1 = "https://api.playkit.ai";
|
|
4240
4318
|
class TranscriptionProvider {
|
|
4241
4319
|
constructor(authManager, config) {
|
|
4242
4320
|
this.authManager = authManager;
|
|
@@ -4301,10 +4379,7 @@ class TranscriptionProvider {
|
|
|
4301
4379
|
try {
|
|
4302
4380
|
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
4303
4381
|
method: 'POST',
|
|
4304
|
-
headers: {
|
|
4305
|
-
Authorization: `Bearer ${token}`,
|
|
4306
|
-
'Content-Type': 'application/json',
|
|
4307
|
-
},
|
|
4382
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
4308
4383
|
body: JSON.stringify(requestBody),
|
|
4309
4384
|
});
|
|
4310
4385
|
if (!response.ok) {
|
|
@@ -4450,9 +4525,18 @@ class StreamParser {
|
|
|
4450
4525
|
if (text) {
|
|
4451
4526
|
yield yield __await(text);
|
|
4452
4527
|
}
|
|
4453
|
-
|
|
4528
|
+
// Stream termination events
|
|
4529
|
+
if (parsed.type === 'done' || parsed.type === 'finish' || parsed.finish_reason) {
|
|
4454
4530
|
return yield __await(void 0);
|
|
4455
4531
|
}
|
|
4532
|
+
if (parsed.type === 'abort') {
|
|
4533
|
+
// Server-side timeout or cancellation — treat as end of stream
|
|
4534
|
+
return yield __await(void 0);
|
|
4535
|
+
}
|
|
4536
|
+
if (parsed.type === 'error') {
|
|
4537
|
+
// Server-side error event — throw to trigger onError callback
|
|
4538
|
+
throw new Error(parsed.errorText || parsed.error || 'Stream error');
|
|
4539
|
+
}
|
|
4456
4540
|
}
|
|
4457
4541
|
catch (error) {
|
|
4458
4542
|
// If JSON parse fails, treat as plain text
|
|
@@ -4551,6 +4635,18 @@ class StreamParser {
|
|
|
4551
4635
|
/**
|
|
4552
4636
|
* Chat client for AI text generation
|
|
4553
4637
|
*/
|
|
4638
|
+
/**
|
|
4639
|
+
* Helper to extract string from MessageContent
|
|
4640
|
+
*/
|
|
4641
|
+
function contentToString(content) {
|
|
4642
|
+
if (!content)
|
|
4643
|
+
return '';
|
|
4644
|
+
if (typeof content === 'string')
|
|
4645
|
+
return content;
|
|
4646
|
+
// For array of content parts, extract text parts
|
|
4647
|
+
const textParts = content.filter(part => part.type === 'text');
|
|
4648
|
+
return textParts.map(part => part.text).join('');
|
|
4649
|
+
}
|
|
4554
4650
|
class ChatClient {
|
|
4555
4651
|
constructor(provider, model) {
|
|
4556
4652
|
this.schemaLibrary = null;
|
|
@@ -4600,7 +4696,7 @@ class ChatClient {
|
|
|
4600
4696
|
throw new Error('No choices in response');
|
|
4601
4697
|
}
|
|
4602
4698
|
return {
|
|
4603
|
-
content: choice.message.content,
|
|
4699
|
+
content: contentToString(choice.message.content),
|
|
4604
4700
|
model: response.model,
|
|
4605
4701
|
finishReason: choice.finish_reason,
|
|
4606
4702
|
usage: response.usage
|
|
@@ -4671,9 +4767,10 @@ class ChatClient {
|
|
|
4671
4767
|
}
|
|
4672
4768
|
// Extract user message content from the last user message
|
|
4673
4769
|
const lastUserMessage = [...messages].reverse().find(m => m.role === 'user');
|
|
4674
|
-
const prompt = (lastUserMessage === null || lastUserMessage === void 0 ? void 0 : lastUserMessage.content)
|
|
4770
|
+
const prompt = contentToString(lastUserMessage === null || lastUserMessage === void 0 ? void 0 : lastUserMessage.content);
|
|
4675
4771
|
// Build system message from messages array
|
|
4676
|
-
const
|
|
4772
|
+
const systemMessageContent = (_a = messages.find(m => m.role === 'system')) === null || _a === void 0 ? void 0 : _a.content;
|
|
4773
|
+
const systemMessage = contentToString(systemMessageContent) || undefined;
|
|
4677
4774
|
return this.generateStructuredWithSchema(schemaEntry.schema, prompt, Object.assign({ schemaName, schemaDescription: schemaEntry.description, systemMessage }, options));
|
|
4678
4775
|
}
|
|
4679
4776
|
/**
|
|
@@ -4764,7 +4861,7 @@ class ChatClient {
|
|
|
4764
4861
|
throw new Error('No choices in response');
|
|
4765
4862
|
}
|
|
4766
4863
|
return {
|
|
4767
|
-
content: choice.message.content
|
|
4864
|
+
content: contentToString(choice.message.content),
|
|
4768
4865
|
model: response.model,
|
|
4769
4866
|
finishReason: choice.finish_reason,
|
|
4770
4867
|
usage: response.usage
|
|
@@ -4828,7 +4925,7 @@ class GeneratedImageImpl {
|
|
|
4828
4925
|
return new Promise((resolve, reject) => {
|
|
4829
4926
|
const img = new Image();
|
|
4830
4927
|
img.onload = () => resolve(img);
|
|
4831
|
-
img.onerror = (
|
|
4928
|
+
img.onerror = (_e) => reject(new Error('Failed to load image'));
|
|
4832
4929
|
img.src = this.toDataURL();
|
|
4833
4930
|
});
|
|
4834
4931
|
}
|
|
@@ -4842,13 +4939,14 @@ class ImageClient {
|
|
|
4842
4939
|
* Generate a single image
|
|
4843
4940
|
*/
|
|
4844
4941
|
async generateImage(config) {
|
|
4942
|
+
var _a;
|
|
4845
4943
|
const imageConfig = Object.assign(Object.assign({}, config), { model: config.model || this.model, n: 1 });
|
|
4846
4944
|
const response = await this.provider.generateImages(imageConfig);
|
|
4847
4945
|
const imageData = response.data[0];
|
|
4848
4946
|
if (!imageData || !imageData.b64_json) {
|
|
4849
4947
|
throw new Error('No image data in response');
|
|
4850
4948
|
}
|
|
4851
|
-
return new GeneratedImageImpl(imageData.b64_json, config.prompt, imageData.revised_prompt, config.size, imageData.b64_json_original, imageData.transparent_success);
|
|
4949
|
+
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);
|
|
4852
4950
|
}
|
|
4853
4951
|
/**
|
|
4854
4952
|
* Generate multiple images
|
|
@@ -4857,10 +4955,11 @@ class ImageClient {
|
|
|
4857
4955
|
const imageConfig = Object.assign(Object.assign({}, config), { model: config.model || this.model, n: config.n || 1 });
|
|
4858
4956
|
const response = await this.provider.generateImages(imageConfig);
|
|
4859
4957
|
return response.data.map((imageData) => {
|
|
4958
|
+
var _a;
|
|
4860
4959
|
if (!imageData.b64_json) {
|
|
4861
4960
|
throw new Error('No image data in response');
|
|
4862
4961
|
}
|
|
4863
|
-
return new GeneratedImageImpl(imageData.b64_json, config.prompt, imageData.revised_prompt, config.size, imageData.b64_json_original, imageData.transparent_success);
|
|
4962
|
+
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);
|
|
4864
4963
|
});
|
|
4865
4964
|
}
|
|
4866
4965
|
/**
|
|
@@ -4979,1019 +5078,1117 @@ class TranscriptionClient {
|
|
|
4979
5078
|
}
|
|
4980
5079
|
|
|
4981
5080
|
/**
|
|
4982
|
-
*
|
|
4983
|
-
* Automatically handles conversation history
|
|
5081
|
+
* Global AI Context Manager for managing NPC conversations and player context.
|
|
4984
5082
|
*
|
|
4985
|
-
*
|
|
4986
|
-
* -
|
|
4987
|
-
* -
|
|
4988
|
-
* -
|
|
4989
|
-
* - Automatic conversation history management
|
|
5083
|
+
* Features:
|
|
5084
|
+
* - Player description management
|
|
5085
|
+
* - NPC conversation tracking
|
|
5086
|
+
* - Automatic conversation compaction (AutoCompact)
|
|
4990
5087
|
*/
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
5088
|
+
/**
|
|
5089
|
+
* Global AI Context Manager
|
|
5090
|
+
* Manages NPC conversations and player context across the application
|
|
5091
|
+
*/
|
|
5092
|
+
class AIContextManager extends EventEmitter {
|
|
5093
|
+
constructor(config) {
|
|
5094
|
+
var _a, _b, _c, _d, _e;
|
|
4994
5095
|
super();
|
|
4995
|
-
this.
|
|
4996
|
-
this.
|
|
4997
|
-
this.
|
|
4998
|
-
|
|
4999
|
-
this.
|
|
5000
|
-
this.
|
|
5001
|
-
this.
|
|
5002
|
-
this.
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5096
|
+
this.playerDescription = null;
|
|
5097
|
+
this.playerPrompt = null;
|
|
5098
|
+
this.playerMemories = new Map();
|
|
5099
|
+
this.npcStates = new Map();
|
|
5100
|
+
this.autoCompactTimer = null;
|
|
5101
|
+
this.chatClientFactory = null;
|
|
5102
|
+
this.logger = Logger.getLogger('AIContextManager');
|
|
5103
|
+
this.config = {
|
|
5104
|
+
enableAutoCompact: (_a = config === null || config === void 0 ? void 0 : config.enableAutoCompact) !== null && _a !== void 0 ? _a : false,
|
|
5105
|
+
autoCompactMinMessages: (_b = config === null || config === void 0 ? void 0 : config.autoCompactMinMessages) !== null && _b !== void 0 ? _b : 20,
|
|
5106
|
+
autoCompactTimeoutSeconds: (_c = config === null || config === void 0 ? void 0 : config.autoCompactTimeoutSeconds) !== null && _c !== void 0 ? _c : 300,
|
|
5107
|
+
autoCompactCheckInterval: (_d = config === null || config === void 0 ? void 0 : config.autoCompactCheckInterval) !== null && _d !== void 0 ? _d : 60000,
|
|
5108
|
+
fastModel: (_e = config === null || config === void 0 ? void 0 : config.fastModel) !== null && _e !== void 0 ? _e : '',
|
|
5109
|
+
};
|
|
5110
|
+
// Start auto-compact check if enabled
|
|
5111
|
+
if (this.config.enableAutoCompact) {
|
|
5112
|
+
this.startAutoCompactCheck();
|
|
5113
|
+
}
|
|
5007
5114
|
}
|
|
5008
|
-
// =====
|
|
5115
|
+
// ===== Singleton Pattern =====
|
|
5009
5116
|
/**
|
|
5010
|
-
*
|
|
5117
|
+
* Get the singleton instance of AIContextManager
|
|
5118
|
+
* Creates a new instance if one doesn't exist
|
|
5011
5119
|
*/
|
|
5012
|
-
|
|
5013
|
-
|
|
5120
|
+
static getInstance(config) {
|
|
5121
|
+
if (!AIContextManager._instance) {
|
|
5122
|
+
AIContextManager._instance = new AIContextManager(config);
|
|
5123
|
+
}
|
|
5124
|
+
return AIContextManager._instance;
|
|
5014
5125
|
}
|
|
5015
|
-
// ===== Character Design & Memory System =====
|
|
5016
5126
|
/**
|
|
5017
|
-
*
|
|
5018
|
-
* The system prompt is composed of CharacterDesign + all Memories.
|
|
5127
|
+
* Reset the singleton instance (useful for testing)
|
|
5019
5128
|
*/
|
|
5020
|
-
|
|
5021
|
-
|
|
5129
|
+
static resetInstance() {
|
|
5130
|
+
if (AIContextManager._instance) {
|
|
5131
|
+
AIContextManager._instance.destroy();
|
|
5132
|
+
AIContextManager._instance = null;
|
|
5133
|
+
}
|
|
5022
5134
|
}
|
|
5135
|
+
// ===== Configuration =====
|
|
5023
5136
|
/**
|
|
5024
|
-
*
|
|
5137
|
+
* Set the chat client factory for creating chat clients for summarization
|
|
5138
|
+
* Required for compaction to work
|
|
5025
5139
|
*/
|
|
5026
|
-
|
|
5027
|
-
|
|
5140
|
+
setChatClientFactory(factory) {
|
|
5141
|
+
this.chatClientFactory = factory;
|
|
5028
5142
|
}
|
|
5029
5143
|
/**
|
|
5030
|
-
*
|
|
5031
|
-
* This method is kept for backwards compatibility.
|
|
5144
|
+
* Update configuration
|
|
5032
5145
|
*/
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
this.
|
|
5146
|
+
setConfig(config) {
|
|
5147
|
+
const wasAutoCompactEnabled = this.config.enableAutoCompact;
|
|
5148
|
+
this.config = Object.assign(Object.assign({}, this.config), config);
|
|
5149
|
+
// Handle auto-compact state change
|
|
5150
|
+
if (config.enableAutoCompact !== undefined) {
|
|
5151
|
+
if (config.enableAutoCompact && !wasAutoCompactEnabled) {
|
|
5152
|
+
this.startAutoCompactCheck();
|
|
5153
|
+
}
|
|
5154
|
+
else if (!config.enableAutoCompact && wasAutoCompactEnabled) {
|
|
5155
|
+
this.stopAutoCompactCheck();
|
|
5156
|
+
}
|
|
5157
|
+
}
|
|
5036
5158
|
}
|
|
5159
|
+
// ===== Player Description =====
|
|
5037
5160
|
/**
|
|
5038
|
-
*
|
|
5039
|
-
*
|
|
5161
|
+
* Set the player's description for AI context.
|
|
5162
|
+
* Used when generating reply predictions and for NPC context.
|
|
5163
|
+
* @param description Description of the player character
|
|
5040
5164
|
*/
|
|
5041
|
-
|
|
5042
|
-
|
|
5165
|
+
setPlayerDescription(description) {
|
|
5166
|
+
this.playerDescription = description;
|
|
5167
|
+
this.emit('playerDescriptionChanged', description);
|
|
5043
5168
|
}
|
|
5044
5169
|
/**
|
|
5045
|
-
*
|
|
5046
|
-
*
|
|
5170
|
+
* Get the current player description.
|
|
5171
|
+
* @returns The player description, or null if not set
|
|
5172
|
+
*/
|
|
5173
|
+
getPlayerDescription() {
|
|
5174
|
+
return this.playerDescription;
|
|
5175
|
+
}
|
|
5176
|
+
/**
|
|
5177
|
+
* Clear the player description.
|
|
5178
|
+
*/
|
|
5179
|
+
clearPlayerDescription() {
|
|
5180
|
+
this.playerDescription = null;
|
|
5181
|
+
this.emit('playerDescriptionChanged', null);
|
|
5182
|
+
}
|
|
5183
|
+
// ===== Player Prompt & Memory (for Reply Prediction) =====
|
|
5184
|
+
/**
|
|
5185
|
+
* Set the player's character prompt/persona.
|
|
5186
|
+
* This defines how the player character speaks and behaves.
|
|
5187
|
+
* Used when generating reply predictions to match the player's tone.
|
|
5188
|
+
* @param prompt The player character's persona/prompt
|
|
5189
|
+
*/
|
|
5190
|
+
setPlayerPrompt(prompt) {
|
|
5191
|
+
this.playerPrompt = prompt;
|
|
5192
|
+
}
|
|
5193
|
+
/**
|
|
5194
|
+
* Get the current player prompt.
|
|
5195
|
+
* @returns The player prompt, or null if not set
|
|
5196
|
+
*/
|
|
5197
|
+
getPlayerPrompt() {
|
|
5198
|
+
return this.playerPrompt;
|
|
5199
|
+
}
|
|
5200
|
+
/**
|
|
5201
|
+
* Set or update a memory for the player character.
|
|
5202
|
+
* Memories are appended to the player prompt to form the full player context.
|
|
5047
5203
|
* Set memoryContent to null or empty to remove the memory.
|
|
5048
5204
|
* @param memoryName The name/key of the memory
|
|
5049
5205
|
* @param memoryContent The content of the memory. Null or empty to remove.
|
|
5050
5206
|
*/
|
|
5051
|
-
|
|
5207
|
+
setPlayerMemory(memoryName, memoryContent) {
|
|
5052
5208
|
if (!memoryName) {
|
|
5053
5209
|
this.logger.warn('Memory name cannot be empty');
|
|
5054
5210
|
return;
|
|
5055
5211
|
}
|
|
5056
5212
|
if (!memoryContent) {
|
|
5057
5213
|
// Remove memory if content is null or empty
|
|
5058
|
-
|
|
5059
|
-
this.memories.delete(memoryName);
|
|
5060
|
-
this.emit('memory_removed', memoryName);
|
|
5061
|
-
}
|
|
5214
|
+
this.playerMemories.delete(memoryName);
|
|
5062
5215
|
}
|
|
5063
5216
|
else {
|
|
5064
5217
|
// Add or update memory
|
|
5065
|
-
this.
|
|
5066
|
-
this.emit('memory_set', memoryName, memoryContent);
|
|
5218
|
+
this.playerMemories.set(memoryName, memoryContent);
|
|
5067
5219
|
}
|
|
5068
5220
|
}
|
|
5069
5221
|
/**
|
|
5070
|
-
* Get a specific memory by name.
|
|
5222
|
+
* Get a specific player memory by name.
|
|
5071
5223
|
* @param memoryName The name of the memory to retrieve
|
|
5072
5224
|
* @returns The memory content, or undefined if not found
|
|
5073
5225
|
*/
|
|
5074
|
-
|
|
5075
|
-
return this.
|
|
5226
|
+
getPlayerMemory(memoryName) {
|
|
5227
|
+
return this.playerMemories.get(memoryName);
|
|
5076
5228
|
}
|
|
5077
5229
|
/**
|
|
5078
|
-
* Get all memory names currently stored.
|
|
5230
|
+
* Get all player memory names currently stored.
|
|
5079
5231
|
* @returns Array of memory names
|
|
5080
5232
|
*/
|
|
5081
|
-
|
|
5082
|
-
return Array.from(this.
|
|
5233
|
+
getPlayerMemoryNames() {
|
|
5234
|
+
return Array.from(this.playerMemories.keys());
|
|
5083
5235
|
}
|
|
5084
5236
|
/**
|
|
5085
|
-
* Clear all memories (but keep
|
|
5237
|
+
* Clear all player memories (but keep player prompt).
|
|
5086
5238
|
*/
|
|
5087
|
-
|
|
5088
|
-
this.
|
|
5089
|
-
this.emit('memories_cleared');
|
|
5239
|
+
clearPlayerMemories() {
|
|
5240
|
+
this.playerMemories.clear();
|
|
5090
5241
|
}
|
|
5091
5242
|
/**
|
|
5092
|
-
* Build the complete
|
|
5243
|
+
* Build the complete player context from PlayerPrompt + PlayerMemories.
|
|
5244
|
+
* Used by NPCClient for generating reply predictions.
|
|
5245
|
+
* @returns The combined player context string, or null if no context is set
|
|
5093
5246
|
*/
|
|
5094
|
-
|
|
5247
|
+
buildPlayerContext() {
|
|
5095
5248
|
const parts = [];
|
|
5096
|
-
if (this.
|
|
5097
|
-
parts.push(this.
|
|
5249
|
+
if (this.playerPrompt) {
|
|
5250
|
+
parts.push(this.playerPrompt);
|
|
5098
5251
|
}
|
|
5099
|
-
if (this.
|
|
5100
|
-
const memoryStrings = Array.from(this.
|
|
5252
|
+
if (this.playerMemories.size > 0) {
|
|
5253
|
+
const memoryStrings = Array.from(this.playerMemories.entries())
|
|
5101
5254
|
.map(([name, content]) => `[${name}]: ${content}`);
|
|
5102
|
-
parts.push('Memories:\n' + memoryStrings.join('\n'));
|
|
5255
|
+
parts.push('Player Memories:\n' + memoryStrings.join('\n'));
|
|
5256
|
+
}
|
|
5257
|
+
if (parts.length === 0) {
|
|
5258
|
+
return null;
|
|
5103
5259
|
}
|
|
5104
5260
|
return parts.join('\n\n');
|
|
5105
5261
|
}
|
|
5106
|
-
// =====
|
|
5262
|
+
// ===== NPC Tracking =====
|
|
5107
5263
|
/**
|
|
5108
|
-
*
|
|
5264
|
+
* Register an NPC for context management.
|
|
5265
|
+
* @param npc The NPC client to register
|
|
5109
5266
|
*/
|
|
5110
|
-
|
|
5111
|
-
|
|
5267
|
+
registerNpc(npc) {
|
|
5268
|
+
if (!npc)
|
|
5269
|
+
return;
|
|
5270
|
+
if (!this.npcStates.has(npc)) {
|
|
5271
|
+
this.npcStates.set(npc, {
|
|
5272
|
+
lastConversationTime: new Date(),
|
|
5273
|
+
isCompacted: false,
|
|
5274
|
+
compactionCount: 0,
|
|
5275
|
+
});
|
|
5276
|
+
}
|
|
5112
5277
|
}
|
|
5113
5278
|
/**
|
|
5114
|
-
*
|
|
5279
|
+
* Unregister an NPC (call when NPC is destroyed/removed).
|
|
5280
|
+
* @param npc The NPC client to unregister
|
|
5115
5281
|
*/
|
|
5116
|
-
|
|
5117
|
-
|
|
5282
|
+
unregisterNpc(npc) {
|
|
5283
|
+
if (!npc)
|
|
5284
|
+
return;
|
|
5285
|
+
this.npcStates.delete(npc);
|
|
5118
5286
|
}
|
|
5119
5287
|
/**
|
|
5120
|
-
*
|
|
5121
|
-
*
|
|
5122
|
-
* @param
|
|
5123
|
-
* @returns Array of predicted player replies, or empty array on failure
|
|
5288
|
+
* Record that a conversation occurred with an NPC.
|
|
5289
|
+
* Called after each Talk() exchange.
|
|
5290
|
+
* @param npc The NPC client that had a conversation
|
|
5124
5291
|
*/
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
if (this.
|
|
5129
|
-
this.
|
|
5130
|
-
return [];
|
|
5131
|
-
}
|
|
5132
|
-
try {
|
|
5133
|
-
// Get last NPC message
|
|
5134
|
-
const lastNpcMessage = (_a = [...this.history]
|
|
5135
|
-
.reverse()
|
|
5136
|
-
.find(m => m.role === 'assistant')) === null || _a === void 0 ? void 0 : _a.content;
|
|
5137
|
-
if (!lastNpcMessage) {
|
|
5138
|
-
this.logger.info('No NPC message found to generate predictions from');
|
|
5139
|
-
return [];
|
|
5140
|
-
}
|
|
5141
|
-
// Build recent history (last 6 non-system messages)
|
|
5142
|
-
const recentHistory = this.history
|
|
5143
|
-
.filter(m => m.role !== 'system')
|
|
5144
|
-
.slice(-6)
|
|
5145
|
-
.map(m => `${m.role}: ${m.content}`);
|
|
5146
|
-
// Build prompt for prediction generation
|
|
5147
|
-
const prompt = `Based on the conversation history below, generate exactly ${predictionNum} natural and contextually appropriate responses that the player might say next.
|
|
5148
|
-
|
|
5149
|
-
Context:
|
|
5150
|
-
- This is a conversation between a player and an NPC in a game
|
|
5151
|
-
- The NPC just said: "${lastNpcMessage}"
|
|
5152
|
-
|
|
5153
|
-
Conversation history:
|
|
5154
|
-
${recentHistory.join('\n')}
|
|
5155
|
-
|
|
5156
|
-
Requirements:
|
|
5157
|
-
1. Each response should be 1-2 sentences maximum
|
|
5158
|
-
2. Responses should be diverse in tone and intent
|
|
5159
|
-
3. Include a mix of questions, statements, and action-oriented responses
|
|
5160
|
-
4. Responses should feel natural for a player character
|
|
5161
|
-
|
|
5162
|
-
Output ONLY a JSON array of ${predictionNum} strings, nothing else:
|
|
5163
|
-
["response1", "response2", "response3", "response4"]`;
|
|
5164
|
-
const result = await this.chatClient.textGeneration({
|
|
5165
|
-
messages: [{ role: 'user', content: prompt }],
|
|
5166
|
-
temperature: 0.8,
|
|
5167
|
-
model: this.fastModel,
|
|
5168
|
-
});
|
|
5169
|
-
if (!result.content) {
|
|
5170
|
-
this.logger.warn('Failed to generate predictions: empty response');
|
|
5171
|
-
return [];
|
|
5172
|
-
}
|
|
5173
|
-
// Parse JSON response
|
|
5174
|
-
const predictions = this.parsePredictionsFromJson(result.content, predictionNum);
|
|
5175
|
-
if (predictions.length > 0) {
|
|
5176
|
-
this.emit('replyPredictions', predictions);
|
|
5177
|
-
}
|
|
5178
|
-
return predictions;
|
|
5179
|
-
}
|
|
5180
|
-
catch (error) {
|
|
5181
|
-
this.logger.error('Error generating predictions:', error);
|
|
5182
|
-
return [];
|
|
5292
|
+
recordConversation(npc) {
|
|
5293
|
+
if (!npc)
|
|
5294
|
+
return;
|
|
5295
|
+
if (!this.npcStates.has(npc)) {
|
|
5296
|
+
this.registerNpc(npc);
|
|
5183
5297
|
}
|
|
5298
|
+
const state = this.npcStates.get(npc);
|
|
5299
|
+
state.lastConversationTime = new Date();
|
|
5300
|
+
state.isCompacted = false; // Reset compaction flag on new conversation
|
|
5184
5301
|
}
|
|
5185
5302
|
/**
|
|
5186
|
-
*
|
|
5303
|
+
* Get all registered NPCs
|
|
5187
5304
|
*/
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
// Try to find JSON array in response
|
|
5191
|
-
const startIndex = response.indexOf('[');
|
|
5192
|
-
const endIndex = response.lastIndexOf(']');
|
|
5193
|
-
if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) {
|
|
5194
|
-
this.logger.warn('Could not find JSON array in prediction response');
|
|
5195
|
-
return this.extractPredictionsFromText(response, expectedCount);
|
|
5196
|
-
}
|
|
5197
|
-
const jsonArray = response.substring(startIndex, endIndex + 1);
|
|
5198
|
-
const parsed = JSON.parse(jsonArray);
|
|
5199
|
-
if (Array.isArray(parsed)) {
|
|
5200
|
-
return parsed
|
|
5201
|
-
.filter(item => typeof item === 'string' && item.trim())
|
|
5202
|
-
.slice(0, expectedCount);
|
|
5203
|
-
}
|
|
5204
|
-
return [];
|
|
5205
|
-
}
|
|
5206
|
-
catch (error) {
|
|
5207
|
-
this.logger.warn('Failed to parse predictions JSON:', error);
|
|
5208
|
-
return this.extractPredictionsFromText(response, expectedCount);
|
|
5209
|
-
}
|
|
5305
|
+
getRegisteredNpcs() {
|
|
5306
|
+
return Array.from(this.npcStates.keys());
|
|
5210
5307
|
}
|
|
5211
5308
|
/**
|
|
5212
|
-
*
|
|
5309
|
+
* Get the conversation state for an NPC
|
|
5213
5310
|
*/
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
const lines = response.split(/[\n\r]+/).filter(line => line.trim());
|
|
5217
|
-
for (const line of lines) {
|
|
5218
|
-
let cleaned = line.trim();
|
|
5219
|
-
// Skip empty lines and JSON brackets
|
|
5220
|
-
if (!cleaned || cleaned === '[' || cleaned === ']')
|
|
5221
|
-
continue;
|
|
5222
|
-
// Remove common prefixes like "1.", "- ", etc.
|
|
5223
|
-
if (/^\d+\./.test(cleaned)) {
|
|
5224
|
-
cleaned = cleaned.replace(/^\d+\.\s*/, '');
|
|
5225
|
-
}
|
|
5226
|
-
else if (cleaned.startsWith('- ')) {
|
|
5227
|
-
cleaned = cleaned.substring(2);
|
|
5228
|
-
}
|
|
5229
|
-
// Remove surrounding quotes
|
|
5230
|
-
if (cleaned.startsWith('"') && cleaned.endsWith('"')) {
|
|
5231
|
-
cleaned = cleaned.slice(1, -1);
|
|
5232
|
-
}
|
|
5233
|
-
// Remove trailing comma
|
|
5234
|
-
if (cleaned.endsWith(',')) {
|
|
5235
|
-
cleaned = cleaned.slice(0, -1).trim();
|
|
5236
|
-
}
|
|
5237
|
-
if (cleaned && predictions.length < expectedCount) {
|
|
5238
|
-
predictions.push(cleaned);
|
|
5239
|
-
}
|
|
5240
|
-
}
|
|
5241
|
-
return predictions;
|
|
5311
|
+
getNpcState(npc) {
|
|
5312
|
+
return this.npcStates.get(npc);
|
|
5242
5313
|
}
|
|
5314
|
+
// ===== Auto Compaction =====
|
|
5243
5315
|
/**
|
|
5244
|
-
*
|
|
5316
|
+
* Check if an NPC is eligible for compaction.
|
|
5317
|
+
* @param npc The NPC to check
|
|
5318
|
+
* @returns True if eligible for compaction
|
|
5245
5319
|
*/
|
|
5246
|
-
|
|
5247
|
-
if (!
|
|
5248
|
-
return;
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5320
|
+
isEligibleForCompaction(npc) {
|
|
5321
|
+
if (!npc)
|
|
5322
|
+
return false;
|
|
5323
|
+
const state = this.npcStates.get(npc);
|
|
5324
|
+
if (!state)
|
|
5325
|
+
return false;
|
|
5326
|
+
// Check if already compacted since last conversation
|
|
5327
|
+
if (state.isCompacted)
|
|
5328
|
+
return false;
|
|
5329
|
+
// Check message count
|
|
5330
|
+
const history = npc.getHistory();
|
|
5331
|
+
const nonSystemMessages = history.filter(m => m.role !== 'system').length;
|
|
5332
|
+
if (nonSystemMessages < this.config.autoCompactMinMessages)
|
|
5333
|
+
return false;
|
|
5334
|
+
// Check time since last conversation
|
|
5335
|
+
const timeSinceLastConversation = (Date.now() - state.lastConversationTime.getTime()) / 1000;
|
|
5336
|
+
if (timeSinceLastConversation < this.config.autoCompactTimeoutSeconds)
|
|
5337
|
+
return false;
|
|
5338
|
+
return true;
|
|
5253
5339
|
}
|
|
5254
|
-
// ===== Main API - Talk Methods =====
|
|
5255
5340
|
/**
|
|
5256
|
-
*
|
|
5341
|
+
* Manually trigger conversation compaction for a specific NPC.
|
|
5342
|
+
* Summarizes the conversation history and stores it as a memory.
|
|
5343
|
+
* @param npc The NPC to compact
|
|
5344
|
+
* @returns True if compaction succeeded
|
|
5257
5345
|
*/
|
|
5258
|
-
async
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
const userMessage = { role: 'user', content: message };
|
|
5263
|
-
this.history.push(userMessage);
|
|
5264
|
-
// Build messages array with system prompt
|
|
5265
|
-
const messages = [
|
|
5266
|
-
{ role: 'system', content: this.buildSystemPrompt() },
|
|
5267
|
-
...this.history,
|
|
5268
|
-
];
|
|
5269
|
-
// Generate response
|
|
5270
|
-
const result = await this.chatClient.textGeneration({
|
|
5271
|
-
messages,
|
|
5272
|
-
temperature: this.temperature,
|
|
5273
|
-
});
|
|
5274
|
-
// Add assistant response to history
|
|
5275
|
-
const assistantMessage = { role: 'assistant', content: result.content };
|
|
5276
|
-
this.history.push(assistantMessage);
|
|
5277
|
-
// Trim history if needed
|
|
5278
|
-
this.trimHistory();
|
|
5279
|
-
this.emit('response', result.content);
|
|
5280
|
-
// Trigger reply prediction generation (fire and forget)
|
|
5281
|
-
this.triggerReplyPrediction();
|
|
5282
|
-
return result.content;
|
|
5283
|
-
}
|
|
5284
|
-
finally {
|
|
5285
|
-
this._isTalking = false;
|
|
5346
|
+
async compactConversation(npc) {
|
|
5347
|
+
if (!npc) {
|
|
5348
|
+
this.logger.warn('Cannot compact: NPC is null');
|
|
5349
|
+
return false;
|
|
5286
5350
|
}
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
*/
|
|
5291
|
-
async talkStream(message, onChunk, onComplete) {
|
|
5292
|
-
this._isTalking = true;
|
|
5293
|
-
try {
|
|
5294
|
-
// Add user message to history
|
|
5295
|
-
const userMessage = { role: 'user', content: message };
|
|
5296
|
-
this.history.push(userMessage);
|
|
5297
|
-
// Build messages array with system prompt
|
|
5298
|
-
const messages = [
|
|
5299
|
-
{ role: 'system', content: this.buildSystemPrompt() },
|
|
5300
|
-
...this.history,
|
|
5301
|
-
];
|
|
5302
|
-
// Generate response
|
|
5303
|
-
await this.chatClient.textGenerationStream({
|
|
5304
|
-
messages,
|
|
5305
|
-
temperature: this.temperature,
|
|
5306
|
-
onChunk,
|
|
5307
|
-
onComplete: (fullText) => {
|
|
5308
|
-
this._isTalking = false;
|
|
5309
|
-
// Add assistant response to history
|
|
5310
|
-
const assistantMessage = { role: 'assistant', content: fullText };
|
|
5311
|
-
this.history.push(assistantMessage);
|
|
5312
|
-
// Trim history if needed
|
|
5313
|
-
this.trimHistory();
|
|
5314
|
-
this.emit('response', fullText);
|
|
5315
|
-
// Trigger reply prediction generation (fire and forget)
|
|
5316
|
-
this.triggerReplyPrediction();
|
|
5317
|
-
if (onComplete) {
|
|
5318
|
-
onComplete(fullText);
|
|
5319
|
-
}
|
|
5320
|
-
},
|
|
5321
|
-
});
|
|
5351
|
+
if (!this.chatClientFactory) {
|
|
5352
|
+
this.logger.error('Cannot compact: No chat client factory set. Call setChatClientFactory() first.');
|
|
5353
|
+
return false;
|
|
5322
5354
|
}
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5355
|
+
const history = npc.getHistory();
|
|
5356
|
+
const nonSystemMessages = history.filter(m => m.role !== 'system');
|
|
5357
|
+
if (nonSystemMessages.length < 2) {
|
|
5358
|
+
this.logger.info('Skipping compaction: not enough messages');
|
|
5359
|
+
return false;
|
|
5326
5360
|
}
|
|
5327
|
-
}
|
|
5328
|
-
/**
|
|
5329
|
-
* Talk with structured output
|
|
5330
|
-
* @deprecated Use talkWithActions instead for NPC decision-making with actions
|
|
5331
|
-
*/
|
|
5332
|
-
async talkStructured(message, schemaName) {
|
|
5333
|
-
this.logger.warn('talkStructured is deprecated. Use talkWithActions instead for NPC decision-making with actions.');
|
|
5334
|
-
// Add user message to history
|
|
5335
|
-
const userMessage = { role: 'user', content: message };
|
|
5336
|
-
this.history.push(userMessage);
|
|
5337
|
-
// Generate structured response
|
|
5338
|
-
const result = await this.chatClient.generateStructured({
|
|
5339
|
-
schemaName,
|
|
5340
|
-
prompt: message,
|
|
5341
|
-
messages: [{ role: 'system', content: this.buildSystemPrompt() }, ...this.history],
|
|
5342
|
-
temperature: this.temperature,
|
|
5343
|
-
});
|
|
5344
|
-
// Add a text representation to history
|
|
5345
|
-
const assistantMessage = {
|
|
5346
|
-
role: 'assistant',
|
|
5347
|
-
content: JSON.stringify(result),
|
|
5348
|
-
};
|
|
5349
|
-
this.history.push(assistantMessage);
|
|
5350
|
-
this.trimHistory();
|
|
5351
|
-
return result;
|
|
5352
|
-
}
|
|
5353
|
-
/**
|
|
5354
|
-
* Talk to the NPC with available actions (non-streaming)
|
|
5355
|
-
* @param message The message to send
|
|
5356
|
-
* @param actions List of actions the NPC can perform
|
|
5357
|
-
* @returns Response containing text and any action calls
|
|
5358
|
-
*/
|
|
5359
|
-
async talkWithActions(message, actions) {
|
|
5360
|
-
this._isTalking = true;
|
|
5361
5361
|
try {
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5362
|
+
this.logger.info(`Starting compaction (${nonSystemMessages.length} messages)`);
|
|
5363
|
+
// Build conversation text for summarization
|
|
5364
|
+
const conversationText = nonSystemMessages
|
|
5365
|
+
.map(m => `${m.role}: ${m.content}`)
|
|
5366
|
+
.join('\n');
|
|
5367
|
+
// Create summarization prompt
|
|
5368
|
+
const summaryPrompt = `Summarize the following conversation concisely. Focus on:
|
|
5369
|
+
1. Key topics discussed
|
|
5370
|
+
2. Important information exchanged
|
|
5371
|
+
3. Any decisions or commitments made
|
|
5372
|
+
4. The emotional tone
|
|
5373
|
+
|
|
5374
|
+
Keep the summary under 200 words. Write in third person.
|
|
5375
|
+
|
|
5376
|
+
Conversation:
|
|
5377
|
+
${conversationText}`;
|
|
5378
|
+
// Use chat client for summarization
|
|
5379
|
+
const chatClient = this.chatClientFactory();
|
|
5380
|
+
const result = await chatClient.textGeneration({
|
|
5381
|
+
messages: [{ role: 'user', content: summaryPrompt }],
|
|
5382
|
+
temperature: 0.5,
|
|
5383
|
+
model: this.config.fastModel || undefined,
|
|
5380
5384
|
});
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
};
|
|
5387
|
-
// Extract tool calls if any
|
|
5388
|
-
if (result.tool_calls) {
|
|
5389
|
-
response.actionCalls = result.tool_calls.map(tc => ({
|
|
5390
|
-
id: tc.id,
|
|
5391
|
-
actionName: tc.function.name,
|
|
5392
|
-
arguments: this.parseToolArguments(tc.function.arguments),
|
|
5393
|
-
}));
|
|
5394
|
-
response.hasActions = response.actionCalls.length > 0;
|
|
5395
|
-
}
|
|
5396
|
-
// Add assistant response to history
|
|
5397
|
-
const assistantMessage = {
|
|
5398
|
-
role: 'assistant',
|
|
5399
|
-
content: response.text,
|
|
5400
|
-
tool_calls: result.tool_calls,
|
|
5401
|
-
};
|
|
5402
|
-
this.history.push(assistantMessage);
|
|
5403
|
-
this.trimHistory();
|
|
5404
|
-
this.emit('response', response.text);
|
|
5405
|
-
if (response.hasActions) {
|
|
5406
|
-
this.emit('actions', response.actionCalls);
|
|
5385
|
+
if (!result.content) {
|
|
5386
|
+
const error = 'Empty response from summarization';
|
|
5387
|
+
this.logger.error(`Compaction failed: ${error}`);
|
|
5388
|
+
this.emit('compactionFailed', npc, error);
|
|
5389
|
+
return false;
|
|
5407
5390
|
}
|
|
5408
|
-
//
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
async talkWithActionsStream(message, actions, onChunk, onComplete) {
|
|
5421
|
-
this._isTalking = true;
|
|
5422
|
-
try {
|
|
5423
|
-
// Add user message to history
|
|
5424
|
-
const userMessage = { role: 'user', content: message };
|
|
5425
|
-
this.history.push(userMessage);
|
|
5426
|
-
// Convert NpcActions to ChatTools
|
|
5427
|
-
const tools = actions
|
|
5428
|
-
.filter(a => a && a.enabled !== false)
|
|
5429
|
-
.map(a => npcActionToTool(a));
|
|
5430
|
-
// Build messages array with system prompt
|
|
5431
|
-
const messages = [
|
|
5432
|
-
{ role: 'system', content: this.buildSystemPrompt() },
|
|
5433
|
-
...this.history,
|
|
5434
|
-
];
|
|
5435
|
-
// Generate response with tools (streaming)
|
|
5436
|
-
await this.chatClient.textGenerationWithToolsStream({
|
|
5437
|
-
messages,
|
|
5438
|
-
temperature: this.temperature,
|
|
5439
|
-
tools,
|
|
5440
|
-
tool_choice: 'auto',
|
|
5441
|
-
onChunk,
|
|
5442
|
-
onComplete: (result) => {
|
|
5443
|
-
this._isTalking = false;
|
|
5444
|
-
// Build response
|
|
5445
|
-
const response = {
|
|
5446
|
-
text: result.content || '',
|
|
5447
|
-
actionCalls: [],
|
|
5448
|
-
hasActions: false,
|
|
5449
|
-
};
|
|
5450
|
-
// Extract tool calls if any
|
|
5451
|
-
if (result.tool_calls) {
|
|
5452
|
-
response.actionCalls = result.tool_calls.map(tc => ({
|
|
5453
|
-
id: tc.id,
|
|
5454
|
-
actionName: tc.function.name,
|
|
5455
|
-
arguments: this.parseToolArguments(tc.function.arguments),
|
|
5456
|
-
}));
|
|
5457
|
-
response.hasActions = response.actionCalls.length > 0;
|
|
5458
|
-
}
|
|
5459
|
-
// Add assistant response to history
|
|
5460
|
-
const assistantMessage = {
|
|
5461
|
-
role: 'assistant',
|
|
5462
|
-
content: response.text,
|
|
5463
|
-
tool_calls: result.tool_calls,
|
|
5464
|
-
};
|
|
5465
|
-
this.history.push(assistantMessage);
|
|
5466
|
-
this.trimHistory();
|
|
5467
|
-
this.emit('response', response.text);
|
|
5468
|
-
if (response.hasActions) {
|
|
5469
|
-
this.emit('actions', response.actionCalls);
|
|
5470
|
-
}
|
|
5471
|
-
// Trigger reply prediction generation (fire and forget)
|
|
5472
|
-
this.triggerReplyPrediction();
|
|
5473
|
-
if (onComplete) {
|
|
5474
|
-
onComplete(response);
|
|
5475
|
-
}
|
|
5476
|
-
},
|
|
5477
|
-
});
|
|
5391
|
+
// Clear history and add summary as memory
|
|
5392
|
+
npc.clearHistory();
|
|
5393
|
+
npc.setMemory('PreviousConversationSummary', result.content);
|
|
5394
|
+
// Update state
|
|
5395
|
+
const state = this.npcStates.get(npc);
|
|
5396
|
+
if (state) {
|
|
5397
|
+
state.isCompacted = true;
|
|
5398
|
+
state.compactionCount++;
|
|
5399
|
+
}
|
|
5400
|
+
this.logger.info(`Compaction completed. Summary: ${result.content.substring(0, 100)}...`);
|
|
5401
|
+
this.emit('npcCompacted', npc);
|
|
5402
|
+
return true;
|
|
5478
5403
|
}
|
|
5479
5404
|
catch (error) {
|
|
5480
|
-
|
|
5481
|
-
|
|
5405
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5406
|
+
this.logger.error(`Compaction error: ${errorMessage}`);
|
|
5407
|
+
this.emit('compactionFailed', npc, errorMessage);
|
|
5408
|
+
return false;
|
|
5482
5409
|
}
|
|
5483
5410
|
}
|
|
5484
|
-
// ===== Action Results Reporting =====
|
|
5485
5411
|
/**
|
|
5486
|
-
*
|
|
5487
|
-
*
|
|
5412
|
+
* Compact all registered NPCs that meet the eligibility criteria.
|
|
5413
|
+
* @returns Number of NPCs successfully compacted
|
|
5488
5414
|
*/
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
|
|
5415
|
+
async compactAllEligible() {
|
|
5416
|
+
const eligibleNpcs = Array.from(this.npcStates.keys()).filter(npc => this.isEligibleForCompaction(npc));
|
|
5417
|
+
if (eligibleNpcs.length === 0) {
|
|
5418
|
+
return 0;
|
|
5419
|
+
}
|
|
5420
|
+
this.logger.info(`Compacting ${eligibleNpcs.length} eligible NPCs`);
|
|
5421
|
+
let successCount = 0;
|
|
5422
|
+
for (const npc of eligibleNpcs) {
|
|
5423
|
+
const success = await this.compactConversation(npc);
|
|
5424
|
+
if (success)
|
|
5425
|
+
successCount++;
|
|
5496
5426
|
}
|
|
5427
|
+
return successCount;
|
|
5497
5428
|
}
|
|
5429
|
+
// ===== Auto Compact Timer =====
|
|
5498
5430
|
/**
|
|
5499
|
-
*
|
|
5431
|
+
* Start the auto-compact check timer
|
|
5500
5432
|
*/
|
|
5501
|
-
|
|
5502
|
-
this.
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5433
|
+
startAutoCompactCheck() {
|
|
5434
|
+
if (this.autoCompactTimer) {
|
|
5435
|
+
this.stopAutoCompactCheck();
|
|
5436
|
+
}
|
|
5437
|
+
this.autoCompactTimer = setInterval(() => {
|
|
5438
|
+
this.runAutoCompactCheck();
|
|
5439
|
+
}, this.config.autoCompactCheckInterval);
|
|
5507
5440
|
}
|
|
5508
5441
|
/**
|
|
5509
|
-
*
|
|
5442
|
+
* Stop the auto-compact check timer
|
|
5510
5443
|
*/
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5444
|
+
stopAutoCompactCheck() {
|
|
5445
|
+
if (this.autoCompactTimer) {
|
|
5446
|
+
clearInterval(this.autoCompactTimer);
|
|
5447
|
+
this.autoCompactTimer = null;
|
|
5514
5448
|
}
|
|
5515
|
-
|
|
5516
|
-
|
|
5449
|
+
}
|
|
5450
|
+
/**
|
|
5451
|
+
* Run a single auto-compact check
|
|
5452
|
+
*/
|
|
5453
|
+
async runAutoCompactCheck() {
|
|
5454
|
+
if (!this.config.enableAutoCompact)
|
|
5455
|
+
return;
|
|
5456
|
+
const eligibleNpcs = Array.from(this.npcStates.keys()).filter(npc => this.isEligibleForCompaction(npc));
|
|
5457
|
+
for (const npc of eligibleNpcs) {
|
|
5458
|
+
// Fire and forget - don't block
|
|
5459
|
+
this.compactConversation(npc).catch(err => {
|
|
5460
|
+
this.logger.error('Auto-compact error:', err);
|
|
5461
|
+
});
|
|
5517
5462
|
}
|
|
5518
5463
|
}
|
|
5519
|
-
// =====
|
|
5464
|
+
// ===== Lifecycle =====
|
|
5520
5465
|
/**
|
|
5521
|
-
*
|
|
5466
|
+
* Enable auto-compaction
|
|
5522
5467
|
*/
|
|
5523
|
-
|
|
5524
|
-
|
|
5468
|
+
enableAutoCompact() {
|
|
5469
|
+
this.config.enableAutoCompact = true;
|
|
5470
|
+
this.startAutoCompactCheck();
|
|
5525
5471
|
}
|
|
5526
5472
|
/**
|
|
5527
|
-
*
|
|
5473
|
+
* Disable auto-compaction
|
|
5528
5474
|
*/
|
|
5529
|
-
|
|
5530
|
-
|
|
5475
|
+
disableAutoCompact() {
|
|
5476
|
+
this.config.enableAutoCompact = false;
|
|
5477
|
+
this.stopAutoCompactCheck();
|
|
5531
5478
|
}
|
|
5532
5479
|
/**
|
|
5533
|
-
*
|
|
5534
|
-
* The character design and memories will be preserved.
|
|
5480
|
+
* Clean up resources
|
|
5535
5481
|
*/
|
|
5536
|
-
|
|
5482
|
+
destroy() {
|
|
5483
|
+
this.stopAutoCompactCheck();
|
|
5484
|
+
this.npcStates.clear();
|
|
5485
|
+
this.playerDescription = null;
|
|
5486
|
+
this.playerPrompt = null;
|
|
5487
|
+
this.playerMemories.clear();
|
|
5488
|
+
this.removeAllListeners();
|
|
5489
|
+
}
|
|
5490
|
+
}
|
|
5491
|
+
AIContextManager._instance = null;
|
|
5492
|
+
/**
|
|
5493
|
+
* Default AIContextManager instance
|
|
5494
|
+
* Can be used as a global context manager
|
|
5495
|
+
*/
|
|
5496
|
+
const defaultContextManager = AIContextManager.getInstance();
|
|
5497
|
+
|
|
5498
|
+
/**
|
|
5499
|
+
* NPC Client for simplified conversation management
|
|
5500
|
+
* Automatically handles conversation history
|
|
5501
|
+
*
|
|
5502
|
+
* Key Features:
|
|
5503
|
+
* - Call talk() for all interactions - actions are handled automatically
|
|
5504
|
+
* - Memory system for persistent NPC context
|
|
5505
|
+
* - Reply prediction for suggesting player responses
|
|
5506
|
+
* - Automatic conversation history management
|
|
5507
|
+
*/
|
|
5508
|
+
class NPCClient extends EventEmitter {
|
|
5509
|
+
constructor(chatClient, config) {
|
|
5510
|
+
var _a, _b, _c;
|
|
5511
|
+
super();
|
|
5512
|
+
this._isTalking = false;
|
|
5513
|
+
this.logger = Logger.getLogger('NPCClient');
|
|
5514
|
+
this.chatClient = chatClient;
|
|
5515
|
+
// Support both characterDesign and legacy systemPrompt
|
|
5516
|
+
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.';
|
|
5517
|
+
this.temperature = (_a = config === null || config === void 0 ? void 0 : config.temperature) !== null && _a !== void 0 ? _a : 0.7;
|
|
5518
|
+
this.maxHistoryLength = (config === null || config === void 0 ? void 0 : config.maxHistoryLength) || 50;
|
|
5519
|
+
this.generateReplyPrediction = (_b = config === null || config === void 0 ? void 0 : config.generateReplyPrediction) !== null && _b !== void 0 ? _b : false;
|
|
5520
|
+
this.predictionCount = Math.max(2, Math.min(6, (_c = config === null || config === void 0 ? void 0 : config.predictionCount) !== null && _c !== void 0 ? _c : 4));
|
|
5521
|
+
this.fastModel = config === null || config === void 0 ? void 0 : config.fastModel;
|
|
5537
5522
|
this.history = [];
|
|
5538
|
-
this.
|
|
5523
|
+
this.memories = new Map();
|
|
5539
5524
|
}
|
|
5525
|
+
// ===== State Properties =====
|
|
5540
5526
|
/**
|
|
5541
|
-
*
|
|
5542
|
-
* @returns true if reverted, false if not enough history
|
|
5527
|
+
* Whether the NPC is currently processing a request
|
|
5543
5528
|
*/
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5529
|
+
get isTalking() {
|
|
5530
|
+
return this._isTalking;
|
|
5531
|
+
}
|
|
5532
|
+
// ===== Character Design & Memory System =====
|
|
5533
|
+
/**
|
|
5534
|
+
* Set the character design for the NPC.
|
|
5535
|
+
* The system prompt is composed of CharacterDesign + all Memories.
|
|
5536
|
+
*/
|
|
5537
|
+
setCharacterDesign(design) {
|
|
5538
|
+
this.characterDesign = design;
|
|
5539
|
+
}
|
|
5540
|
+
/**
|
|
5541
|
+
* Get the current character design
|
|
5542
|
+
*/
|
|
5543
|
+
getCharacterDesign() {
|
|
5544
|
+
return this.characterDesign;
|
|
5545
|
+
}
|
|
5546
|
+
/**
|
|
5547
|
+
* @deprecated Use setCharacterDesign instead.
|
|
5548
|
+
* This method is kept for backwards compatibility.
|
|
5549
|
+
*/
|
|
5550
|
+
setSystemPrompt(prompt) {
|
|
5551
|
+
this.logger.warn('setSystemPrompt is deprecated. Use setCharacterDesign instead.');
|
|
5552
|
+
this.setCharacterDesign(prompt);
|
|
5553
|
+
}
|
|
5554
|
+
/**
|
|
5555
|
+
* @deprecated Use getCharacterDesign instead.
|
|
5556
|
+
* This method is kept for backwards compatibility.
|
|
5557
|
+
*/
|
|
5558
|
+
getSystemPrompt() {
|
|
5559
|
+
return this.buildSystemPrompt();
|
|
5560
|
+
}
|
|
5561
|
+
/**
|
|
5562
|
+
* Set or update a memory for the NPC.
|
|
5563
|
+
* Memories are appended to the character design to form the system prompt.
|
|
5564
|
+
* Set memoryContent to null or empty to remove the memory.
|
|
5565
|
+
* @param memoryName The name/key of the memory
|
|
5566
|
+
* @param memoryContent The content of the memory. Null or empty to remove.
|
|
5567
|
+
*/
|
|
5568
|
+
setMemory(memoryName, memoryContent) {
|
|
5569
|
+
if (!memoryName) {
|
|
5570
|
+
this.logger.warn('Memory name cannot be empty');
|
|
5571
|
+
return;
|
|
5572
|
+
}
|
|
5573
|
+
if (!memoryContent) {
|
|
5574
|
+
// Remove memory if content is null or empty
|
|
5575
|
+
if (this.memories.has(memoryName)) {
|
|
5576
|
+
this.memories.delete(memoryName);
|
|
5577
|
+
this.emit('memory_removed', memoryName);
|
|
5554
5578
|
}
|
|
5555
5579
|
}
|
|
5556
|
-
|
|
5557
|
-
//
|
|
5558
|
-
this.
|
|
5559
|
-
this.
|
|
5560
|
-
this.emit('history_reverted');
|
|
5561
|
-
return true;
|
|
5580
|
+
else {
|
|
5581
|
+
// Add or update memory
|
|
5582
|
+
this.memories.set(memoryName, memoryContent);
|
|
5583
|
+
this.emit('memory_set', memoryName, memoryContent);
|
|
5562
5584
|
}
|
|
5563
|
-
return false;
|
|
5564
5585
|
}
|
|
5565
5586
|
/**
|
|
5566
|
-
*
|
|
5567
|
-
* @param
|
|
5568
|
-
* @returns
|
|
5587
|
+
* Get a specific memory by name.
|
|
5588
|
+
* @param memoryName The name of the memory to retrieve
|
|
5589
|
+
* @returns The memory content, or undefined if not found
|
|
5569
5590
|
*/
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5591
|
+
getMemory(memoryName) {
|
|
5592
|
+
return this.memories.get(memoryName);
|
|
5593
|
+
}
|
|
5594
|
+
/**
|
|
5595
|
+
* Get all memory names currently stored.
|
|
5596
|
+
* @returns Array of memory names
|
|
5597
|
+
*/
|
|
5598
|
+
getMemoryNames() {
|
|
5599
|
+
return Array.from(this.memories.keys());
|
|
5600
|
+
}
|
|
5601
|
+
/**
|
|
5602
|
+
* Clear all memories (but keep character design).
|
|
5603
|
+
*/
|
|
5604
|
+
clearMemories() {
|
|
5605
|
+
this.memories.clear();
|
|
5606
|
+
this.emit('memories_cleared');
|
|
5607
|
+
}
|
|
5608
|
+
/**
|
|
5609
|
+
* Build the complete system prompt from CharacterDesign + Memories.
|
|
5610
|
+
*/
|
|
5611
|
+
buildSystemPrompt() {
|
|
5612
|
+
const parts = [];
|
|
5613
|
+
if (this.characterDesign) {
|
|
5614
|
+
parts.push(this.characterDesign);
|
|
5579
5615
|
}
|
|
5580
|
-
|
|
5616
|
+
if (this.memories.size > 0) {
|
|
5617
|
+
const memoryStrings = Array.from(this.memories.entries())
|
|
5618
|
+
.map(([name, content]) => `[${name}]: ${content}`);
|
|
5619
|
+
parts.push('Memories:\n' + memoryStrings.join('\n'));
|
|
5620
|
+
}
|
|
5621
|
+
return parts.join('\n\n');
|
|
5581
5622
|
}
|
|
5623
|
+
// ===== Reply Prediction =====
|
|
5582
5624
|
/**
|
|
5583
|
-
*
|
|
5584
|
-
* @deprecated Use revertHistory() or revertChatMessages() instead
|
|
5625
|
+
* Enable or disable automatic reply prediction
|
|
5585
5626
|
*/
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
this.history = this.history.slice(0, index + 1);
|
|
5589
|
-
this.emit('history_reverted', index);
|
|
5590
|
-
}
|
|
5627
|
+
setGenerateReplyPrediction(enabled) {
|
|
5628
|
+
this.generateReplyPrediction = enabled;
|
|
5591
5629
|
}
|
|
5592
5630
|
/**
|
|
5593
|
-
*
|
|
5631
|
+
* Set the number of predictions to generate
|
|
5594
5632
|
*/
|
|
5595
|
-
|
|
5596
|
-
this.
|
|
5597
|
-
this.trimHistory();
|
|
5633
|
+
setPredictionCount(count) {
|
|
5634
|
+
this.predictionCount = Math.max(2, Math.min(6, count));
|
|
5598
5635
|
}
|
|
5599
5636
|
/**
|
|
5600
|
-
*
|
|
5637
|
+
* Manually generate reply predictions based on current conversation.
|
|
5638
|
+
* Uses the fast model for quick generation.
|
|
5639
|
+
* @param tempPrompt Optional temporary prompt to influence the prediction style/tone
|
|
5640
|
+
* @param count Number of predictions to generate (default: uses predictionCount property)
|
|
5641
|
+
* @returns Array of predicted player replies, or empty array on failure
|
|
5601
5642
|
*/
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5643
|
+
async generateReplyPredictions(tempPrompt, count) {
|
|
5644
|
+
var _a;
|
|
5645
|
+
const predictionNum = count !== null && count !== void 0 ? count : this.predictionCount;
|
|
5646
|
+
if (this.history.length < 2) {
|
|
5647
|
+
this.logger.info('Not enough conversation history to generate predictions');
|
|
5648
|
+
return [];
|
|
5649
|
+
}
|
|
5650
|
+
try {
|
|
5651
|
+
// Get last NPC message
|
|
5652
|
+
const lastNpcMessage = (_a = [...this.history]
|
|
5653
|
+
.reverse()
|
|
5654
|
+
.find(m => m.role === 'assistant')) === null || _a === void 0 ? void 0 : _a.content;
|
|
5655
|
+
if (!lastNpcMessage) {
|
|
5656
|
+
this.logger.info('No NPC message found to generate predictions from');
|
|
5657
|
+
return [];
|
|
5658
|
+
}
|
|
5659
|
+
// Build recent history (last 6 non-system messages)
|
|
5660
|
+
const recentHistory = this.history
|
|
5661
|
+
.filter(m => m.role !== 'system')
|
|
5662
|
+
.slice(-6)
|
|
5663
|
+
.map(m => `${m.role}: ${m.content}`);
|
|
5664
|
+
// Get player context from AIContextManager
|
|
5665
|
+
const contextManager = AIContextManager.getInstance();
|
|
5666
|
+
const playerContext = contextManager.buildPlayerContext();
|
|
5667
|
+
// Build player character section
|
|
5668
|
+
let playerCharacterSection = '';
|
|
5669
|
+
if (playerContext || tempPrompt) {
|
|
5670
|
+
playerCharacterSection = '\nPlayer Character:\n';
|
|
5671
|
+
if (playerContext) {
|
|
5672
|
+
playerCharacterSection += playerContext + '\n';
|
|
5673
|
+
}
|
|
5674
|
+
if (tempPrompt) {
|
|
5675
|
+
playerCharacterSection += `Additional guidance: ${tempPrompt}\n`;
|
|
5676
|
+
}
|
|
5677
|
+
}
|
|
5678
|
+
// Build prompt for prediction generation
|
|
5679
|
+
const prompt = `Based on the conversation history below, generate exactly ${predictionNum} natural and contextually appropriate responses that the player might say next.
|
|
5680
|
+
|
|
5681
|
+
Context:
|
|
5682
|
+
- This is a conversation between a player and an NPC in a game
|
|
5683
|
+
- The NPC just said: "${lastNpcMessage}"
|
|
5684
|
+
${playerCharacterSection}
|
|
5685
|
+
Conversation history:
|
|
5686
|
+
${recentHistory.join('\n')}
|
|
5687
|
+
|
|
5688
|
+
Requirements:
|
|
5689
|
+
1. Each response should be 1-2 sentences maximum
|
|
5690
|
+
2. Responses should be diverse in tone and intent
|
|
5691
|
+
3. Include a mix of questions, statements, and action-oriented responses
|
|
5692
|
+
4. Responses should feel natural for the player character${playerContext || tempPrompt ? ' and match their personality/tone' : ''}
|
|
5693
|
+
|
|
5694
|
+
Output ONLY a JSON array of ${predictionNum} strings, nothing else:
|
|
5695
|
+
["response1", "response2", "response3", "response4"]`;
|
|
5696
|
+
const result = await this.chatClient.textGeneration({
|
|
5697
|
+
messages: [{ role: 'user', content: prompt }],
|
|
5698
|
+
temperature: 0.8,
|
|
5699
|
+
model: this.fastModel,
|
|
5700
|
+
});
|
|
5701
|
+
if (!result.content) {
|
|
5702
|
+
this.logger.warn('Failed to generate predictions: empty response');
|
|
5703
|
+
return [];
|
|
5704
|
+
}
|
|
5705
|
+
// Parse JSON response
|
|
5706
|
+
const predictions = this.parsePredictionsFromJson(result.content, predictionNum);
|
|
5707
|
+
if (predictions.length > 0) {
|
|
5708
|
+
this.emit('replyPredictions', predictions);
|
|
5709
|
+
}
|
|
5710
|
+
return predictions;
|
|
5711
|
+
}
|
|
5712
|
+
catch (error) {
|
|
5713
|
+
this.logger.error('Error generating predictions:', error);
|
|
5714
|
+
return [];
|
|
5606
5715
|
}
|
|
5607
|
-
this.appendMessage({ role: role, content });
|
|
5608
5716
|
}
|
|
5609
5717
|
/**
|
|
5610
|
-
*
|
|
5718
|
+
* Parse predictions from JSON array response
|
|
5611
5719
|
*/
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
//
|
|
5615
|
-
|
|
5720
|
+
parsePredictionsFromJson(response, expectedCount) {
|
|
5721
|
+
try {
|
|
5722
|
+
// Try to find JSON array in response
|
|
5723
|
+
const startIndex = response.indexOf('[');
|
|
5724
|
+
const endIndex = response.lastIndexOf(']');
|
|
5725
|
+
if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) {
|
|
5726
|
+
this.logger.warn('Could not find JSON array in prediction response');
|
|
5727
|
+
return this.extractPredictionsFromText(response, expectedCount);
|
|
5728
|
+
}
|
|
5729
|
+
const jsonArray = response.substring(startIndex, endIndex + 1);
|
|
5730
|
+
const parsed = JSON.parse(jsonArray);
|
|
5731
|
+
if (Array.isArray(parsed)) {
|
|
5732
|
+
return parsed
|
|
5733
|
+
.filter(item => typeof item === 'string' && item.trim())
|
|
5734
|
+
.slice(0, expectedCount);
|
|
5735
|
+
}
|
|
5736
|
+
return [];
|
|
5737
|
+
}
|
|
5738
|
+
catch (error) {
|
|
5739
|
+
this.logger.warn('Failed to parse predictions JSON:', error);
|
|
5740
|
+
return this.extractPredictionsFromText(response, expectedCount);
|
|
5616
5741
|
}
|
|
5617
5742
|
}
|
|
5618
|
-
// ===== Save/Load =====
|
|
5619
5743
|
/**
|
|
5620
|
-
*
|
|
5621
|
-
* Includes characterDesign, memories, and history.
|
|
5744
|
+
* Fallback: Extract predictions from text when JSON parsing fails
|
|
5622
5745
|
*/
|
|
5623
|
-
|
|
5624
|
-
const
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5746
|
+
extractPredictionsFromText(response, expectedCount) {
|
|
5747
|
+
const predictions = [];
|
|
5748
|
+
const lines = response.split(/[\n\r]+/).filter(line => line.trim());
|
|
5749
|
+
for (const line of lines) {
|
|
5750
|
+
let cleaned = line.trim();
|
|
5751
|
+
// Skip empty lines and JSON brackets
|
|
5752
|
+
if (!cleaned || cleaned === '[' || cleaned === ']')
|
|
5753
|
+
continue;
|
|
5754
|
+
// Remove common prefixes like "1.", "- ", etc.
|
|
5755
|
+
if (/^\d+\./.test(cleaned)) {
|
|
5756
|
+
cleaned = cleaned.replace(/^\d+\.\s*/, '');
|
|
5757
|
+
}
|
|
5758
|
+
else if (cleaned.startsWith('- ')) {
|
|
5759
|
+
cleaned = cleaned.substring(2);
|
|
5760
|
+
}
|
|
5761
|
+
// Remove surrounding quotes
|
|
5762
|
+
if (cleaned.startsWith('"') && cleaned.endsWith('"')) {
|
|
5763
|
+
cleaned = cleaned.slice(1, -1);
|
|
5764
|
+
}
|
|
5765
|
+
// Remove trailing comma
|
|
5766
|
+
if (cleaned.endsWith(',')) {
|
|
5767
|
+
cleaned = cleaned.slice(0, -1).trim();
|
|
5768
|
+
}
|
|
5769
|
+
if (cleaned && predictions.length < expectedCount) {
|
|
5770
|
+
predictions.push(cleaned);
|
|
5771
|
+
}
|
|
5772
|
+
}
|
|
5773
|
+
return predictions;
|
|
5630
5774
|
}
|
|
5631
5775
|
/**
|
|
5632
|
-
*
|
|
5633
|
-
* Restores characterDesign, memories, and history.
|
|
5776
|
+
* Internal method to trigger prediction generation after NPC response
|
|
5634
5777
|
*/
|
|
5635
|
-
|
|
5778
|
+
async triggerReplyPrediction() {
|
|
5779
|
+
if (!this.generateReplyPrediction)
|
|
5780
|
+
return;
|
|
5781
|
+
// Fire and forget - don't block the main response
|
|
5782
|
+
this.generateReplyPredictions().catch(err => {
|
|
5783
|
+
this.logger.error('Background prediction generation failed:', err);
|
|
5784
|
+
});
|
|
5785
|
+
}
|
|
5786
|
+
// ===== Main API - Talk Methods =====
|
|
5787
|
+
/**
|
|
5788
|
+
* Talk to the NPC (non-streaming)
|
|
5789
|
+
*/
|
|
5790
|
+
async talk(message) {
|
|
5791
|
+
this._isTalking = true;
|
|
5636
5792
|
try {
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
this.
|
|
5640
|
-
//
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
this.
|
|
5656
|
-
|
|
5793
|
+
// Add user message to history
|
|
5794
|
+
const userMessage = { role: 'user', content: message };
|
|
5795
|
+
this.history.push(userMessage);
|
|
5796
|
+
// Build messages array with system prompt
|
|
5797
|
+
const messages = [
|
|
5798
|
+
{ role: 'system', content: this.buildSystemPrompt() },
|
|
5799
|
+
...this.history,
|
|
5800
|
+
];
|
|
5801
|
+
// Generate response
|
|
5802
|
+
const result = await this.chatClient.textGeneration({
|
|
5803
|
+
messages,
|
|
5804
|
+
temperature: this.temperature,
|
|
5805
|
+
});
|
|
5806
|
+
// Add assistant response to history
|
|
5807
|
+
const assistantMessage = { role: 'assistant', content: result.content };
|
|
5808
|
+
this.history.push(assistantMessage);
|
|
5809
|
+
// Trim history if needed
|
|
5810
|
+
this.trimHistory();
|
|
5811
|
+
this.emit('response', result.content);
|
|
5812
|
+
// Trigger reply prediction generation (fire and forget)
|
|
5813
|
+
this.triggerReplyPrediction();
|
|
5814
|
+
return result.content;
|
|
5657
5815
|
}
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
/**
|
|
5662
|
-
* Global AI Context Manager for managing NPC conversations and player context.
|
|
5663
|
-
*
|
|
5664
|
-
* Features:
|
|
5665
|
-
* - Player description management
|
|
5666
|
-
* - NPC conversation tracking
|
|
5667
|
-
* - Automatic conversation compaction (AutoCompact)
|
|
5668
|
-
*/
|
|
5669
|
-
/**
|
|
5670
|
-
* Global AI Context Manager
|
|
5671
|
-
* Manages NPC conversations and player context across the application
|
|
5672
|
-
*/
|
|
5673
|
-
class AIContextManager extends EventEmitter {
|
|
5674
|
-
constructor(config) {
|
|
5675
|
-
var _a, _b, _c, _d, _e;
|
|
5676
|
-
super();
|
|
5677
|
-
this.playerDescription = null;
|
|
5678
|
-
this.npcStates = new Map();
|
|
5679
|
-
this.autoCompactTimer = null;
|
|
5680
|
-
this.chatClientFactory = null;
|
|
5681
|
-
this.logger = Logger.getLogger('AIContextManager');
|
|
5682
|
-
this.config = {
|
|
5683
|
-
enableAutoCompact: (_a = config === null || config === void 0 ? void 0 : config.enableAutoCompact) !== null && _a !== void 0 ? _a : false,
|
|
5684
|
-
autoCompactMinMessages: (_b = config === null || config === void 0 ? void 0 : config.autoCompactMinMessages) !== null && _b !== void 0 ? _b : 20,
|
|
5685
|
-
autoCompactTimeoutSeconds: (_c = config === null || config === void 0 ? void 0 : config.autoCompactTimeoutSeconds) !== null && _c !== void 0 ? _c : 300,
|
|
5686
|
-
autoCompactCheckInterval: (_d = config === null || config === void 0 ? void 0 : config.autoCompactCheckInterval) !== null && _d !== void 0 ? _d : 60000,
|
|
5687
|
-
fastModel: (_e = config === null || config === void 0 ? void 0 : config.fastModel) !== null && _e !== void 0 ? _e : '',
|
|
5688
|
-
};
|
|
5689
|
-
// Start auto-compact check if enabled
|
|
5690
|
-
if (this.config.enableAutoCompact) {
|
|
5691
|
-
this.startAutoCompactCheck();
|
|
5816
|
+
finally {
|
|
5817
|
+
this._isTalking = false;
|
|
5692
5818
|
}
|
|
5693
5819
|
}
|
|
5694
|
-
// ===== Singleton Pattern =====
|
|
5695
5820
|
/**
|
|
5696
|
-
*
|
|
5697
|
-
* Creates a new instance if one doesn't exist
|
|
5821
|
+
* Talk to the NPC with streaming
|
|
5698
5822
|
*/
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5823
|
+
async talkStream(message, onChunk, onComplete) {
|
|
5824
|
+
this._isTalking = true;
|
|
5825
|
+
try {
|
|
5826
|
+
// Add user message to history
|
|
5827
|
+
const userMessage = { role: 'user', content: message };
|
|
5828
|
+
this.history.push(userMessage);
|
|
5829
|
+
// Build messages array with system prompt
|
|
5830
|
+
const messages = [
|
|
5831
|
+
{ role: 'system', content: this.buildSystemPrompt() },
|
|
5832
|
+
...this.history,
|
|
5833
|
+
];
|
|
5834
|
+
// Generate response
|
|
5835
|
+
await this.chatClient.textGenerationStream({
|
|
5836
|
+
messages,
|
|
5837
|
+
temperature: this.temperature,
|
|
5838
|
+
onChunk,
|
|
5839
|
+
onComplete: (fullText) => {
|
|
5840
|
+
this._isTalking = false;
|
|
5841
|
+
// Add assistant response to history
|
|
5842
|
+
const assistantMessage = { role: 'assistant', content: fullText };
|
|
5843
|
+
this.history.push(assistantMessage);
|
|
5844
|
+
// Trim history if needed
|
|
5845
|
+
this.trimHistory();
|
|
5846
|
+
this.emit('response', fullText);
|
|
5847
|
+
// Trigger reply prediction generation (fire and forget)
|
|
5848
|
+
this.triggerReplyPrediction();
|
|
5849
|
+
if (onComplete) {
|
|
5850
|
+
onComplete(fullText);
|
|
5851
|
+
}
|
|
5852
|
+
},
|
|
5853
|
+
});
|
|
5702
5854
|
}
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
* Reset the singleton instance (useful for testing)
|
|
5707
|
-
*/
|
|
5708
|
-
static resetInstance() {
|
|
5709
|
-
if (AIContextManager._instance) {
|
|
5710
|
-
AIContextManager._instance.destroy();
|
|
5711
|
-
AIContextManager._instance = null;
|
|
5855
|
+
catch (error) {
|
|
5856
|
+
this._isTalking = false;
|
|
5857
|
+
throw error;
|
|
5712
5858
|
}
|
|
5713
5859
|
}
|
|
5714
|
-
// ===== Configuration =====
|
|
5715
5860
|
/**
|
|
5716
|
-
*
|
|
5717
|
-
*
|
|
5861
|
+
* Talk with structured output
|
|
5862
|
+
* @deprecated Use talkWithActions instead for NPC decision-making with actions
|
|
5718
5863
|
*/
|
|
5719
|
-
|
|
5720
|
-
this.
|
|
5864
|
+
async talkStructured(message, schemaName) {
|
|
5865
|
+
this.logger.warn('talkStructured is deprecated. Use talkWithActions instead for NPC decision-making with actions.');
|
|
5866
|
+
// Add user message to history
|
|
5867
|
+
const userMessage = { role: 'user', content: message };
|
|
5868
|
+
this.history.push(userMessage);
|
|
5869
|
+
// Generate structured response
|
|
5870
|
+
const result = await this.chatClient.generateStructured({
|
|
5871
|
+
schemaName,
|
|
5872
|
+
prompt: message,
|
|
5873
|
+
messages: [{ role: 'system', content: this.buildSystemPrompt() }, ...this.history],
|
|
5874
|
+
temperature: this.temperature,
|
|
5875
|
+
});
|
|
5876
|
+
// Add a text representation to history
|
|
5877
|
+
const assistantMessage = {
|
|
5878
|
+
role: 'assistant',
|
|
5879
|
+
content: JSON.stringify(result),
|
|
5880
|
+
};
|
|
5881
|
+
this.history.push(assistantMessage);
|
|
5882
|
+
this.trimHistory();
|
|
5883
|
+
return result;
|
|
5721
5884
|
}
|
|
5722
5885
|
/**
|
|
5723
|
-
*
|
|
5886
|
+
* Talk to the NPC with available actions (non-streaming)
|
|
5887
|
+
* @param message The message to send
|
|
5888
|
+
* @param actions List of actions the NPC can perform
|
|
5889
|
+
* @returns Response containing text and any action calls
|
|
5724
5890
|
*/
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5891
|
+
async talkWithActions(message, actions) {
|
|
5892
|
+
this._isTalking = true;
|
|
5893
|
+
try {
|
|
5894
|
+
// Add user message to history
|
|
5895
|
+
const userMessage = { role: 'user', content: message };
|
|
5896
|
+
this.history.push(userMessage);
|
|
5897
|
+
// Convert NpcActions to ChatTools
|
|
5898
|
+
const tools = actions
|
|
5899
|
+
.filter(a => a && a.enabled !== false)
|
|
5900
|
+
.map(a => npcActionToTool(a));
|
|
5901
|
+
// Build messages array with system prompt
|
|
5902
|
+
const messages = [
|
|
5903
|
+
{ role: 'system', content: this.buildSystemPrompt() },
|
|
5904
|
+
...this.history,
|
|
5905
|
+
];
|
|
5906
|
+
// Generate response with tools
|
|
5907
|
+
const result = await this.chatClient.textGenerationWithTools({
|
|
5908
|
+
messages,
|
|
5909
|
+
temperature: this.temperature,
|
|
5910
|
+
tools,
|
|
5911
|
+
tool_choice: 'auto',
|
|
5912
|
+
});
|
|
5913
|
+
// Build response
|
|
5914
|
+
const response = {
|
|
5915
|
+
text: result.content || '',
|
|
5916
|
+
actionCalls: [],
|
|
5917
|
+
hasActions: false,
|
|
5918
|
+
};
|
|
5919
|
+
// Extract tool calls if any
|
|
5920
|
+
if (result.tool_calls) {
|
|
5921
|
+
response.actionCalls = result.tool_calls.map(tc => ({
|
|
5922
|
+
id: tc.id,
|
|
5923
|
+
actionName: tc.function.name,
|
|
5924
|
+
arguments: this.parseToolArguments(tc.function.arguments),
|
|
5925
|
+
}));
|
|
5926
|
+
response.hasActions = response.actionCalls.length > 0;
|
|
5732
5927
|
}
|
|
5733
|
-
|
|
5734
|
-
|
|
5928
|
+
// Add assistant response to history
|
|
5929
|
+
const assistantMessage = {
|
|
5930
|
+
role: 'assistant',
|
|
5931
|
+
content: response.text,
|
|
5932
|
+
tool_calls: result.tool_calls,
|
|
5933
|
+
};
|
|
5934
|
+
this.history.push(assistantMessage);
|
|
5935
|
+
this.trimHistory();
|
|
5936
|
+
this.emit('response', response.text);
|
|
5937
|
+
if (response.hasActions) {
|
|
5938
|
+
this.emit('actions', response.actionCalls);
|
|
5735
5939
|
}
|
|
5940
|
+
// Trigger reply prediction generation (fire and forget)
|
|
5941
|
+
this.triggerReplyPrediction();
|
|
5942
|
+
return response;
|
|
5943
|
+
}
|
|
5944
|
+
finally {
|
|
5945
|
+
this._isTalking = false;
|
|
5736
5946
|
}
|
|
5737
5947
|
}
|
|
5738
|
-
// ===== Player Description =====
|
|
5739
|
-
/**
|
|
5740
|
-
* Set the player's description for AI context.
|
|
5741
|
-
* Used when generating reply predictions and for NPC context.
|
|
5742
|
-
* @param description Description of the player character
|
|
5743
|
-
*/
|
|
5744
|
-
setPlayerDescription(description) {
|
|
5745
|
-
this.playerDescription = description;
|
|
5746
|
-
this.emit('playerDescriptionChanged', description);
|
|
5747
|
-
}
|
|
5748
|
-
/**
|
|
5749
|
-
* Get the current player description.
|
|
5750
|
-
* @returns The player description, or null if not set
|
|
5751
|
-
*/
|
|
5752
|
-
getPlayerDescription() {
|
|
5753
|
-
return this.playerDescription;
|
|
5754
|
-
}
|
|
5755
|
-
/**
|
|
5756
|
-
* Clear the player description.
|
|
5757
|
-
*/
|
|
5758
|
-
clearPlayerDescription() {
|
|
5759
|
-
this.playerDescription = null;
|
|
5760
|
-
this.emit('playerDescriptionChanged', null);
|
|
5761
|
-
}
|
|
5762
|
-
// ===== NPC Tracking =====
|
|
5763
5948
|
/**
|
|
5764
|
-
*
|
|
5765
|
-
*
|
|
5949
|
+
* Talk to the NPC with actions (streaming)
|
|
5950
|
+
* Text streams first, action calls are returned in onComplete
|
|
5766
5951
|
*/
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5952
|
+
async talkWithActionsStream(message, actions, onChunk, onComplete) {
|
|
5953
|
+
this._isTalking = true;
|
|
5954
|
+
try {
|
|
5955
|
+
// Add user message to history
|
|
5956
|
+
const userMessage = { role: 'user', content: message };
|
|
5957
|
+
this.history.push(userMessage);
|
|
5958
|
+
// Convert NpcActions to ChatTools
|
|
5959
|
+
const tools = actions
|
|
5960
|
+
.filter(a => a && a.enabled !== false)
|
|
5961
|
+
.map(a => npcActionToTool(a));
|
|
5962
|
+
// Build messages array with system prompt
|
|
5963
|
+
const messages = [
|
|
5964
|
+
{ role: 'system', content: this.buildSystemPrompt() },
|
|
5965
|
+
...this.history,
|
|
5966
|
+
];
|
|
5967
|
+
// Generate response with tools (streaming)
|
|
5968
|
+
await this.chatClient.textGenerationWithToolsStream({
|
|
5969
|
+
messages,
|
|
5970
|
+
temperature: this.temperature,
|
|
5971
|
+
tools,
|
|
5972
|
+
tool_choice: 'auto',
|
|
5973
|
+
onChunk,
|
|
5974
|
+
onComplete: (result) => {
|
|
5975
|
+
this._isTalking = false;
|
|
5976
|
+
// Build response
|
|
5977
|
+
const response = {
|
|
5978
|
+
text: result.content || '',
|
|
5979
|
+
actionCalls: [],
|
|
5980
|
+
hasActions: false,
|
|
5981
|
+
};
|
|
5982
|
+
// Extract tool calls if any
|
|
5983
|
+
if (result.tool_calls) {
|
|
5984
|
+
response.actionCalls = result.tool_calls.map(tc => ({
|
|
5985
|
+
id: tc.id,
|
|
5986
|
+
actionName: tc.function.name,
|
|
5987
|
+
arguments: this.parseToolArguments(tc.function.arguments),
|
|
5988
|
+
}));
|
|
5989
|
+
response.hasActions = response.actionCalls.length > 0;
|
|
5990
|
+
}
|
|
5991
|
+
// Add assistant response to history
|
|
5992
|
+
const assistantMessage = {
|
|
5993
|
+
role: 'assistant',
|
|
5994
|
+
content: response.text,
|
|
5995
|
+
tool_calls: result.tool_calls,
|
|
5996
|
+
};
|
|
5997
|
+
this.history.push(assistantMessage);
|
|
5998
|
+
this.trimHistory();
|
|
5999
|
+
this.emit('response', response.text);
|
|
6000
|
+
if (response.hasActions) {
|
|
6001
|
+
this.emit('actions', response.actionCalls);
|
|
6002
|
+
}
|
|
6003
|
+
// Trigger reply prediction generation (fire and forget)
|
|
6004
|
+
this.triggerReplyPrediction();
|
|
6005
|
+
if (onComplete) {
|
|
6006
|
+
onComplete(response);
|
|
6007
|
+
}
|
|
6008
|
+
},
|
|
5775
6009
|
});
|
|
5776
6010
|
}
|
|
6011
|
+
catch (error) {
|
|
6012
|
+
this._isTalking = false;
|
|
6013
|
+
throw error;
|
|
6014
|
+
}
|
|
5777
6015
|
}
|
|
6016
|
+
// ===== Action Results Reporting =====
|
|
5778
6017
|
/**
|
|
5779
|
-
*
|
|
5780
|
-
*
|
|
6018
|
+
* Report action results back to the conversation
|
|
6019
|
+
* Call this after executing actions to let the NPC know the results
|
|
5781
6020
|
*/
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
6021
|
+
reportActionResults(results) {
|
|
6022
|
+
for (const [callId, result] of Object.entries(results)) {
|
|
6023
|
+
this.history.push({
|
|
6024
|
+
role: 'tool',
|
|
6025
|
+
tool_call_id: callId,
|
|
6026
|
+
content: result,
|
|
6027
|
+
});
|
|
6028
|
+
}
|
|
5786
6029
|
}
|
|
5787
6030
|
/**
|
|
5788
|
-
*
|
|
5789
|
-
* Called after each Talk() exchange.
|
|
5790
|
-
* @param npc The NPC client that had a conversation
|
|
6031
|
+
* Report a single action result
|
|
5791
6032
|
*/
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
6033
|
+
reportActionResult(callId, result) {
|
|
6034
|
+
this.history.push({
|
|
6035
|
+
role: 'tool',
|
|
6036
|
+
tool_call_id: callId,
|
|
6037
|
+
content: result,
|
|
6038
|
+
});
|
|
6039
|
+
}
|
|
6040
|
+
/**
|
|
6041
|
+
* Parse tool arguments from JSON string
|
|
6042
|
+
*/
|
|
6043
|
+
parseToolArguments(args) {
|
|
6044
|
+
try {
|
|
6045
|
+
return JSON.parse(args);
|
|
6046
|
+
}
|
|
6047
|
+
catch (_a) {
|
|
6048
|
+
return {};
|
|
5797
6049
|
}
|
|
5798
|
-
const state = this.npcStates.get(npc);
|
|
5799
|
-
state.lastConversationTime = new Date();
|
|
5800
|
-
state.isCompacted = false; // Reset compaction flag on new conversation
|
|
5801
6050
|
}
|
|
6051
|
+
// ===== Conversation History Management =====
|
|
5802
6052
|
/**
|
|
5803
|
-
* Get
|
|
6053
|
+
* Get conversation history
|
|
5804
6054
|
*/
|
|
5805
|
-
|
|
5806
|
-
return
|
|
6055
|
+
getHistory() {
|
|
6056
|
+
return [...this.history];
|
|
5807
6057
|
}
|
|
5808
6058
|
/**
|
|
5809
|
-
* Get the
|
|
6059
|
+
* Get the number of messages in history
|
|
5810
6060
|
*/
|
|
5811
|
-
|
|
5812
|
-
return this.
|
|
6061
|
+
getHistoryLength() {
|
|
6062
|
+
return this.history.length;
|
|
5813
6063
|
}
|
|
5814
|
-
// ===== Auto Compaction =====
|
|
5815
6064
|
/**
|
|
5816
|
-
*
|
|
5817
|
-
*
|
|
5818
|
-
* @returns True if eligible for compaction
|
|
6065
|
+
* Clear conversation history.
|
|
6066
|
+
* The character design and memories will be preserved.
|
|
5819
6067
|
*/
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
const state = this.npcStates.get(npc);
|
|
5824
|
-
if (!state)
|
|
5825
|
-
return false;
|
|
5826
|
-
// Check if already compacted since last conversation
|
|
5827
|
-
if (state.isCompacted)
|
|
5828
|
-
return false;
|
|
5829
|
-
// Check message count
|
|
5830
|
-
const history = npc.getHistory();
|
|
5831
|
-
const nonSystemMessages = history.filter(m => m.role !== 'system').length;
|
|
5832
|
-
if (nonSystemMessages < this.config.autoCompactMinMessages)
|
|
5833
|
-
return false;
|
|
5834
|
-
// Check time since last conversation
|
|
5835
|
-
const timeSinceLastConversation = (Date.now() - state.lastConversationTime.getTime()) / 1000;
|
|
5836
|
-
if (timeSinceLastConversation < this.config.autoCompactTimeoutSeconds)
|
|
5837
|
-
return false;
|
|
5838
|
-
return true;
|
|
6068
|
+
clearHistory() {
|
|
6069
|
+
this.history = [];
|
|
6070
|
+
this.emit('history_cleared');
|
|
5839
6071
|
}
|
|
5840
6072
|
/**
|
|
5841
|
-
*
|
|
5842
|
-
*
|
|
5843
|
-
* @param npc The NPC to compact
|
|
5844
|
-
* @returns True if compaction succeeded
|
|
6073
|
+
* Revert the last exchange (user message and assistant response) from history.
|
|
6074
|
+
* @returns true if reverted, false if not enough history
|
|
5845
6075
|
*/
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
this.logger.error('Cannot compact: No chat client factory set. Call setChatClientFactory() first.');
|
|
5853
|
-
return false;
|
|
5854
|
-
}
|
|
5855
|
-
const history = npc.getHistory();
|
|
5856
|
-
const nonSystemMessages = history.filter(m => m.role !== 'system');
|
|
5857
|
-
if (nonSystemMessages.length < 2) {
|
|
5858
|
-
this.logger.info('Skipping compaction: not enough messages');
|
|
5859
|
-
return false;
|
|
5860
|
-
}
|
|
5861
|
-
try {
|
|
5862
|
-
this.logger.info(`Starting compaction (${nonSystemMessages.length} messages)`);
|
|
5863
|
-
// Build conversation text for summarization
|
|
5864
|
-
const conversationText = nonSystemMessages
|
|
5865
|
-
.map(m => `${m.role}: ${m.content}`)
|
|
5866
|
-
.join('\n');
|
|
5867
|
-
// Create summarization prompt
|
|
5868
|
-
const summaryPrompt = `Summarize the following conversation concisely. Focus on:
|
|
5869
|
-
1. Key topics discussed
|
|
5870
|
-
2. Important information exchanged
|
|
5871
|
-
3. Any decisions or commitments made
|
|
5872
|
-
4. The emotional tone
|
|
5873
|
-
|
|
5874
|
-
Keep the summary under 200 words. Write in third person.
|
|
5875
|
-
|
|
5876
|
-
Conversation:
|
|
5877
|
-
${conversationText}`;
|
|
5878
|
-
// Use chat client for summarization
|
|
5879
|
-
const chatClient = this.chatClientFactory();
|
|
5880
|
-
const result = await chatClient.textGeneration({
|
|
5881
|
-
messages: [{ role: 'user', content: summaryPrompt }],
|
|
5882
|
-
temperature: 0.5,
|
|
5883
|
-
model: this.config.fastModel || undefined,
|
|
5884
|
-
});
|
|
5885
|
-
if (!result.content) {
|
|
5886
|
-
const error = 'Empty response from summarization';
|
|
5887
|
-
this.logger.error(`Compaction failed: ${error}`);
|
|
5888
|
-
this.emit('compactionFailed', npc, error);
|
|
5889
|
-
return false;
|
|
6076
|
+
revertHistory() {
|
|
6077
|
+
let lastAssistantIndex = -1;
|
|
6078
|
+
let lastUserIndex = -1;
|
|
6079
|
+
for (let i = this.history.length - 1; i >= 0; i--) {
|
|
6080
|
+
if (this.history[i].role === 'assistant' && lastAssistantIndex === -1) {
|
|
6081
|
+
lastAssistantIndex = i;
|
|
5890
6082
|
}
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
// Update state
|
|
5895
|
-
const state = this.npcStates.get(npc);
|
|
5896
|
-
if (state) {
|
|
5897
|
-
state.isCompacted = true;
|
|
5898
|
-
state.compactionCount++;
|
|
6083
|
+
else if (this.history[i].role === 'user' && lastAssistantIndex !== -1 && lastUserIndex === -1) {
|
|
6084
|
+
lastUserIndex = i;
|
|
6085
|
+
break;
|
|
5899
6086
|
}
|
|
5900
|
-
this.logger.info(`Compaction completed. Summary: ${result.content.substring(0, 100)}...`);
|
|
5901
|
-
this.emit('npcCompacted', npc);
|
|
5902
|
-
return true;
|
|
5903
6087
|
}
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
this.
|
|
5907
|
-
this.
|
|
5908
|
-
|
|
6088
|
+
if (lastAssistantIndex !== -1 && lastUserIndex !== -1) {
|
|
6089
|
+
// Remove in reverse order to maintain indices
|
|
6090
|
+
this.history.splice(lastAssistantIndex, 1);
|
|
6091
|
+
this.history.splice(lastUserIndex, 1);
|
|
6092
|
+
this.emit('history_reverted');
|
|
6093
|
+
return true;
|
|
5909
6094
|
}
|
|
6095
|
+
return false;
|
|
5910
6096
|
}
|
|
5911
6097
|
/**
|
|
5912
|
-
*
|
|
5913
|
-
* @
|
|
6098
|
+
* Revert (remove) the last N chat messages from history
|
|
6099
|
+
* @param count Number of messages to remove
|
|
6100
|
+
* @returns Number of messages actually removed
|
|
5914
6101
|
*/
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
if (eligibleNpcs.length === 0) {
|
|
6102
|
+
revertChatMessages(count) {
|
|
6103
|
+
if (count <= 0)
|
|
5918
6104
|
return 0;
|
|
6105
|
+
const messagesToRemove = Math.min(count, this.history.length);
|
|
6106
|
+
const originalCount = this.history.length;
|
|
6107
|
+
this.history = this.history.slice(0, -messagesToRemove);
|
|
6108
|
+
const actuallyRemoved = originalCount - this.history.length;
|
|
6109
|
+
if (actuallyRemoved > 0) {
|
|
6110
|
+
this.emit('history_reverted', actuallyRemoved);
|
|
5919
6111
|
}
|
|
5920
|
-
|
|
5921
|
-
let successCount = 0;
|
|
5922
|
-
for (const npc of eligibleNpcs) {
|
|
5923
|
-
const success = await this.compactConversation(npc);
|
|
5924
|
-
if (success)
|
|
5925
|
-
successCount++;
|
|
5926
|
-
}
|
|
5927
|
-
return successCount;
|
|
6112
|
+
return actuallyRemoved;
|
|
5928
6113
|
}
|
|
5929
|
-
// ===== Auto Compact Timer =====
|
|
5930
6114
|
/**
|
|
5931
|
-
*
|
|
6115
|
+
* Revert to a specific point in history
|
|
6116
|
+
* @deprecated Use revertHistory() or revertChatMessages() instead
|
|
5932
6117
|
*/
|
|
5933
|
-
|
|
5934
|
-
if (this.
|
|
5935
|
-
this.
|
|
6118
|
+
revertToMessage(index) {
|
|
6119
|
+
if (index >= 0 && index < this.history.length) {
|
|
6120
|
+
this.history = this.history.slice(0, index + 1);
|
|
6121
|
+
this.emit('history_reverted', index);
|
|
5936
6122
|
}
|
|
5937
|
-
this.autoCompactTimer = setInterval(() => {
|
|
5938
|
-
this.runAutoCompactCheck();
|
|
5939
|
-
}, this.config.autoCompactCheckInterval);
|
|
5940
6123
|
}
|
|
5941
6124
|
/**
|
|
5942
|
-
*
|
|
6125
|
+
* Append a message to history manually
|
|
5943
6126
|
*/
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
this.autoCompactTimer = null;
|
|
5948
|
-
}
|
|
6127
|
+
appendMessage(message) {
|
|
6128
|
+
this.history.push(message);
|
|
6129
|
+
this.trimHistory();
|
|
5949
6130
|
}
|
|
5950
6131
|
/**
|
|
5951
|
-
*
|
|
6132
|
+
* Alias for appendMessage (Unity SDK compatibility)
|
|
5952
6133
|
*/
|
|
5953
|
-
|
|
5954
|
-
if (!
|
|
6134
|
+
appendChatMessage(role, content) {
|
|
6135
|
+
if (!role || !content) {
|
|
6136
|
+
this.logger.warn('Role and content cannot be empty');
|
|
5955
6137
|
return;
|
|
5956
|
-
const eligibleNpcs = Array.from(this.npcStates.keys()).filter(npc => this.isEligibleForCompaction(npc));
|
|
5957
|
-
for (const npc of eligibleNpcs) {
|
|
5958
|
-
// Fire and forget - don't block
|
|
5959
|
-
this.compactConversation(npc).catch(err => {
|
|
5960
|
-
this.logger.error('Auto-compact error:', err);
|
|
5961
|
-
});
|
|
5962
6138
|
}
|
|
6139
|
+
this.appendMessage({ role: role, content });
|
|
5963
6140
|
}
|
|
5964
|
-
// ===== Lifecycle =====
|
|
5965
6141
|
/**
|
|
5966
|
-
*
|
|
6142
|
+
* Trim history to max length
|
|
5967
6143
|
*/
|
|
5968
|
-
|
|
5969
|
-
this.
|
|
5970
|
-
|
|
6144
|
+
trimHistory() {
|
|
6145
|
+
if (this.history.length > this.maxHistoryLength) {
|
|
6146
|
+
// Keep the most recent messages
|
|
6147
|
+
this.history = this.history.slice(-this.maxHistoryLength);
|
|
6148
|
+
}
|
|
5971
6149
|
}
|
|
6150
|
+
// ===== Save/Load =====
|
|
5972
6151
|
/**
|
|
5973
|
-
*
|
|
6152
|
+
* Save the current conversation history to a serializable format.
|
|
6153
|
+
* Includes characterDesign, memories, and history.
|
|
5974
6154
|
*/
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
6155
|
+
saveHistory() {
|
|
6156
|
+
const saveData = {
|
|
6157
|
+
characterDesign: this.characterDesign,
|
|
6158
|
+
memories: Array.from(this.memories.entries()).map(([name, content]) => ({ name, content })),
|
|
6159
|
+
history: this.history,
|
|
6160
|
+
};
|
|
6161
|
+
return JSON.stringify(saveData);
|
|
5978
6162
|
}
|
|
5979
6163
|
/**
|
|
5980
|
-
*
|
|
6164
|
+
* Load conversation history from serialized data.
|
|
6165
|
+
* Restores characterDesign, memories, and history.
|
|
5981
6166
|
*/
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
6167
|
+
loadHistory(saveData) {
|
|
6168
|
+
try {
|
|
6169
|
+
const data = JSON.parse(saveData);
|
|
6170
|
+
// Load character design (with backwards compatibility for old systemPrompt field)
|
|
6171
|
+
this.characterDesign = data.characterDesign || data.systemPrompt || this.characterDesign;
|
|
6172
|
+
// Load memories
|
|
6173
|
+
this.memories.clear();
|
|
6174
|
+
if (data.memories && Array.isArray(data.memories)) {
|
|
6175
|
+
for (const memory of data.memories) {
|
|
6176
|
+
if (memory.name && memory.content) {
|
|
6177
|
+
this.memories.set(memory.name, memory.content);
|
|
6178
|
+
}
|
|
6179
|
+
}
|
|
6180
|
+
}
|
|
6181
|
+
// Load history (skip system messages as they'll be rebuilt from characterDesign + memories)
|
|
6182
|
+
this.history = (data.history || []).filter(m => m.role !== 'system');
|
|
6183
|
+
this.emit('history_loaded');
|
|
6184
|
+
return true;
|
|
6185
|
+
}
|
|
6186
|
+
catch (error) {
|
|
6187
|
+
this.logger.error('Failed to load history:', error);
|
|
6188
|
+
return false;
|
|
6189
|
+
}
|
|
5987
6190
|
}
|
|
5988
6191
|
}
|
|
5989
|
-
AIContextManager._instance = null;
|
|
5990
|
-
/**
|
|
5991
|
-
* Default AIContextManager instance
|
|
5992
|
-
* Can be used as a global context manager
|
|
5993
|
-
*/
|
|
5994
|
-
const defaultContextManager = AIContextManager.getInstance();
|
|
5995
6192
|
|
|
5996
6193
|
/**
|
|
5997
6194
|
* Schema Library for managing JSON schemas for AI structured output generation
|
|
@@ -6350,20 +6547,20 @@ class PlayKitSDK extends EventEmitter {
|
|
|
6350
6547
|
// Create indicator element
|
|
6351
6548
|
this.devTokenIndicator = document.createElement('div');
|
|
6352
6549
|
this.devTokenIndicator.textContent = 'DeveloperToken';
|
|
6353
|
-
this.devTokenIndicator.style.cssText = `
|
|
6354
|
-
position: fixed;
|
|
6355
|
-
top: 10px;
|
|
6356
|
-
left: 10px;
|
|
6357
|
-
background-color: #dc2626;
|
|
6358
|
-
color: white;
|
|
6359
|
-
padding: 4px 12px;
|
|
6360
|
-
border-radius: 4px;
|
|
6361
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
6362
|
-
font-size: 12px;
|
|
6363
|
-
font-weight: 600;
|
|
6364
|
-
z-index: 999999;
|
|
6365
|
-
pointer-events: none;
|
|
6366
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
6550
|
+
this.devTokenIndicator.style.cssText = `
|
|
6551
|
+
position: fixed;
|
|
6552
|
+
top: 10px;
|
|
6553
|
+
left: 10px;
|
|
6554
|
+
background-color: #dc2626;
|
|
6555
|
+
color: white;
|
|
6556
|
+
padding: 4px 12px;
|
|
6557
|
+
border-radius: 4px;
|
|
6558
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
6559
|
+
font-size: 12px;
|
|
6560
|
+
font-weight: 600;
|
|
6561
|
+
z-index: 999999;
|
|
6562
|
+
pointer-events: none;
|
|
6563
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
6367
6564
|
`;
|
|
6368
6565
|
document.body.appendChild(this.devTokenIndicator);
|
|
6369
6566
|
}
|
|
@@ -6752,9 +6949,7 @@ class TokenValidator {
|
|
|
6752
6949
|
*/
|
|
6753
6950
|
async validateToken(token, gameId) {
|
|
6754
6951
|
var _a, _b;
|
|
6755
|
-
const headers = {
|
|
6756
|
-
'Authorization': `Bearer ${token}`,
|
|
6757
|
-
};
|
|
6952
|
+
const headers = Object.assign({ 'Authorization': `Bearer ${token}` }, getSDKHeaders());
|
|
6758
6953
|
if (gameId) {
|
|
6759
6954
|
headers['X-Game-Id'] = gameId;
|
|
6760
6955
|
}
|
|
@@ -6784,9 +6979,7 @@ class TokenValidator {
|
|
|
6784
6979
|
*/
|
|
6785
6980
|
async verifyToken(token, gameId) {
|
|
6786
6981
|
var _a, _b;
|
|
6787
|
-
const headers = {
|
|
6788
|
-
'Authorization': `Bearer ${token}`,
|
|
6789
|
-
};
|
|
6982
|
+
const headers = Object.assign({ 'Authorization': `Bearer ${token}` }, getSDKHeaders());
|
|
6790
6983
|
if (gameId) {
|
|
6791
6984
|
headers['X-Game-Id'] = gameId;
|
|
6792
6985
|
}
|