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