playkit-sdk 1.2.13 → 1.4.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +86 -86
- package/README.md +266 -244
- package/dist/playkit-sdk.cjs.js +1948 -1577
- package/dist/playkit-sdk.cjs.js.map +1 -1
- package/dist/playkit-sdk.d.ts +201 -7
- package/dist/playkit-sdk.esm.js +1948 -1578
- package/dist/playkit-sdk.esm.js.map +1 -1
- package/dist/playkit-sdk.umd.js +2042 -1646
- package/dist/playkit-sdk.umd.js.map +1 -1
- package/package.json +72 -70
package/dist/playkit-sdk.cjs.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* playkit-sdk v1.
|
|
2
|
+
* playkit-sdk v1.4.0-beta.1
|
|
3
3
|
* PlayKit SDK for JavaScript
|
|
4
4
|
* @license SEE LICENSE IN LICENSE
|
|
5
5
|
*/
|
|
@@ -829,6 +829,15 @@ class TokenStorage {
|
|
|
829
829
|
}
|
|
830
830
|
}
|
|
831
831
|
|
|
832
|
+
const SDK_TYPE = 'Javascript';
|
|
833
|
+
const SDK_VERSION = '"1.4.0-beta.1"';
|
|
834
|
+
function getSDKHeaders() {
|
|
835
|
+
return {
|
|
836
|
+
'X-SDK-Type': SDK_TYPE,
|
|
837
|
+
'X-SDK-Version': SDK_VERSION,
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
|
|
832
841
|
/**
|
|
833
842
|
* Authentication Flow Manager
|
|
834
843
|
* Manages the headless authentication flow with automatic UI
|
|
@@ -933,8 +942,8 @@ class AuthFlowManager extends EventEmitter {
|
|
|
933
942
|
constructor(baseURL) {
|
|
934
943
|
super();
|
|
935
944
|
this.currentSessionId = null;
|
|
936
|
-
this.
|
|
937
|
-
this.
|
|
945
|
+
this._uiContainer = null;
|
|
946
|
+
this._isSuccess = false;
|
|
938
947
|
this.currentLanguage = 'en';
|
|
939
948
|
// UI Elements
|
|
940
949
|
this.modal = null;
|
|
@@ -1015,84 +1024,84 @@ class AuthFlowManager extends EventEmitter {
|
|
|
1015
1024
|
// Create modal container
|
|
1016
1025
|
this.modal = document.createElement('div');
|
|
1017
1026
|
this.modal.className = 'playkit-auth-modal';
|
|
1018
|
-
this.modal.innerHTML = `
|
|
1019
|
-
<div class="playkit-auth-overlay"></div>
|
|
1020
|
-
<div class="playkit-auth-container">
|
|
1021
|
-
<!-- Identifier Panel -->
|
|
1022
|
-
<div class="playkit-auth-panel" id="playkit-identifier-panel">
|
|
1023
|
-
<div class="playkit-auth-header">
|
|
1024
|
-
<h2>${this.t('signIn')}</h2>
|
|
1025
|
-
<p>${this.t('signInSubtitle')}</p>
|
|
1026
|
-
</div>
|
|
1027
|
-
|
|
1028
|
-
<div class="playkit-auth-toggle">
|
|
1029
|
-
<label class="playkit-toggle-option">
|
|
1030
|
-
<input type="radio" name="auth-type" value="email" checked>
|
|
1031
|
-
<span>${this.t('email')}</span>
|
|
1032
|
-
</label>
|
|
1033
|
-
<label class="playkit-toggle-option">
|
|
1034
|
-
<input type="radio" name="auth-type" value="phone">
|
|
1035
|
-
<span>${this.t('phone')}</span>
|
|
1036
|
-
</label>
|
|
1037
|
-
</div>
|
|
1038
|
-
|
|
1039
|
-
<div class="playkit-auth-input-group">
|
|
1040
|
-
<div class="playkit-input-wrapper">
|
|
1041
|
-
<svg class="playkit-input-icon" id="playkit-identifier-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1042
|
-
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
|
|
1043
|
-
<polyline points="22,6 12,13 2,6"></polyline>
|
|
1044
|
-
</svg>
|
|
1045
|
-
<input
|
|
1046
|
-
type="text"
|
|
1047
|
-
id="playkit-identifier-input"
|
|
1048
|
-
placeholder="${this.t('emailPlaceholder')}"
|
|
1049
|
-
autocomplete="off"
|
|
1050
|
-
>
|
|
1051
|
-
</div>
|
|
1052
|
-
</div>
|
|
1053
|
-
|
|
1054
|
-
<button class="playkit-auth-button" id="playkit-send-code-btn">
|
|
1055
|
-
${this.t('sendCode')}
|
|
1056
|
-
</button>
|
|
1057
|
-
|
|
1058
|
-
<div class="playkit-auth-error" id="playkit-error-text"></div>
|
|
1059
|
-
</div>
|
|
1060
|
-
|
|
1061
|
-
<!-- Verification Panel -->
|
|
1062
|
-
<div class="playkit-auth-panel" id="playkit-verification-panel" style="display: none;">
|
|
1063
|
-
<div class="playkit-auth-header">
|
|
1064
|
-
<button class="playkit-back-button" id="playkit-back-btn">
|
|
1065
|
-
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1066
|
-
<path d="M19 12H5M12 19l-7-7 7-7"/>
|
|
1067
|
-
</svg>
|
|
1068
|
-
</button>
|
|
1069
|
-
<h2>${this.t('enterCode')}</h2>
|
|
1070
|
-
<p>${this.t('enterCodeSubtitle')} <span id="playkit-identifier-display"></span></p>
|
|
1071
|
-
</div>
|
|
1072
|
-
|
|
1073
|
-
<div class="playkit-auth-input-group">
|
|
1074
|
-
<div class="playkit-code-inputs">
|
|
1075
|
-
<input type="number" maxlength="1" class="playkit-code-input" data-index="0">
|
|
1076
|
-
<input type="number" maxlength="1" class="playkit-code-input" data-index="1">
|
|
1077
|
-
<input type="number" maxlength="1" class="playkit-code-input" data-index="2">
|
|
1078
|
-
<input type="number" maxlength="1" class="playkit-code-input" data-index="3">
|
|
1079
|
-
<input type="number" maxlength="1" class="playkit-code-input" data-index="4">
|
|
1080
|
-
<input type="number" maxlength="1" class="playkit-code-input" data-index="5">
|
|
1081
|
-
</div>
|
|
1082
|
-
</div>
|
|
1083
|
-
|
|
1084
|
-
<button class="playkit-auth-button" id="playkit-verify-btn">
|
|
1085
|
-
${this.t('verify')}
|
|
1086
|
-
</button>
|
|
1087
|
-
|
|
1088
|
-
<div class="playkit-auth-error" id="playkit-verify-error-text"></div>
|
|
1089
|
-
</div>
|
|
1090
|
-
|
|
1091
|
-
<!-- Loading Overlay -->
|
|
1092
|
-
<div class="playkit-loading-overlay" id="playkit-loading-overlay" style="display: none;">
|
|
1093
|
-
<div class="playkit-spinner"></div>
|
|
1094
|
-
</div>
|
|
1095
|
-
</div>
|
|
1027
|
+
this.modal.innerHTML = `
|
|
1028
|
+
<div class="playkit-auth-overlay"></div>
|
|
1029
|
+
<div class="playkit-auth-container">
|
|
1030
|
+
<!-- Identifier Panel -->
|
|
1031
|
+
<div class="playkit-auth-panel" id="playkit-identifier-panel">
|
|
1032
|
+
<div class="playkit-auth-header">
|
|
1033
|
+
<h2>${this.t('signIn')}</h2>
|
|
1034
|
+
<p>${this.t('signInSubtitle')}</p>
|
|
1035
|
+
</div>
|
|
1036
|
+
|
|
1037
|
+
<div class="playkit-auth-toggle">
|
|
1038
|
+
<label class="playkit-toggle-option">
|
|
1039
|
+
<input type="radio" name="auth-type" value="email" checked>
|
|
1040
|
+
<span>${this.t('email')}</span>
|
|
1041
|
+
</label>
|
|
1042
|
+
<label class="playkit-toggle-option">
|
|
1043
|
+
<input type="radio" name="auth-type" value="phone">
|
|
1044
|
+
<span>${this.t('phone')}</span>
|
|
1045
|
+
</label>
|
|
1046
|
+
</div>
|
|
1047
|
+
|
|
1048
|
+
<div class="playkit-auth-input-group">
|
|
1049
|
+
<div class="playkit-input-wrapper">
|
|
1050
|
+
<svg class="playkit-input-icon" id="playkit-identifier-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1051
|
+
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
|
|
1052
|
+
<polyline points="22,6 12,13 2,6"></polyline>
|
|
1053
|
+
</svg>
|
|
1054
|
+
<input
|
|
1055
|
+
type="text"
|
|
1056
|
+
id="playkit-identifier-input"
|
|
1057
|
+
placeholder="${this.t('emailPlaceholder')}"
|
|
1058
|
+
autocomplete="off"
|
|
1059
|
+
>
|
|
1060
|
+
</div>
|
|
1061
|
+
</div>
|
|
1062
|
+
|
|
1063
|
+
<button class="playkit-auth-button" id="playkit-send-code-btn">
|
|
1064
|
+
${this.t('sendCode')}
|
|
1065
|
+
</button>
|
|
1066
|
+
|
|
1067
|
+
<div class="playkit-auth-error" id="playkit-error-text"></div>
|
|
1068
|
+
</div>
|
|
1069
|
+
|
|
1070
|
+
<!-- Verification Panel -->
|
|
1071
|
+
<div class="playkit-auth-panel" id="playkit-verification-panel" style="display: none;">
|
|
1072
|
+
<div class="playkit-auth-header">
|
|
1073
|
+
<button class="playkit-back-button" id="playkit-back-btn">
|
|
1074
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1075
|
+
<path d="M19 12H5M12 19l-7-7 7-7"/>
|
|
1076
|
+
</svg>
|
|
1077
|
+
</button>
|
|
1078
|
+
<h2>${this.t('enterCode')}</h2>
|
|
1079
|
+
<p>${this.t('enterCodeSubtitle')} <span id="playkit-identifier-display"></span></p>
|
|
1080
|
+
</div>
|
|
1081
|
+
|
|
1082
|
+
<div class="playkit-auth-input-group">
|
|
1083
|
+
<div class="playkit-code-inputs">
|
|
1084
|
+
<input type="number" maxlength="1" class="playkit-code-input" data-index="0">
|
|
1085
|
+
<input type="number" maxlength="1" class="playkit-code-input" data-index="1">
|
|
1086
|
+
<input type="number" maxlength="1" class="playkit-code-input" data-index="2">
|
|
1087
|
+
<input type="number" maxlength="1" class="playkit-code-input" data-index="3">
|
|
1088
|
+
<input type="number" maxlength="1" class="playkit-code-input" data-index="4">
|
|
1089
|
+
<input type="number" maxlength="1" class="playkit-code-input" data-index="5">
|
|
1090
|
+
</div>
|
|
1091
|
+
</div>
|
|
1092
|
+
|
|
1093
|
+
<button class="playkit-auth-button" id="playkit-verify-btn">
|
|
1094
|
+
${this.t('verify')}
|
|
1095
|
+
</button>
|
|
1096
|
+
|
|
1097
|
+
<div class="playkit-auth-error" id="playkit-verify-error-text"></div>
|
|
1098
|
+
</div>
|
|
1099
|
+
|
|
1100
|
+
<!-- Loading Overlay -->
|
|
1101
|
+
<div class="playkit-loading-overlay" id="playkit-loading-overlay" style="display: none;">
|
|
1102
|
+
<div class="playkit-spinner"></div>
|
|
1103
|
+
</div>
|
|
1104
|
+
</div>
|
|
1096
1105
|
`;
|
|
1097
1106
|
// Add styles and load VanillaOTP
|
|
1098
1107
|
this.addStyles();
|
|
@@ -1127,274 +1136,274 @@ class AuthFlowManager extends EventEmitter {
|
|
|
1127
1136
|
return;
|
|
1128
1137
|
const style = document.createElement('style');
|
|
1129
1138
|
style.id = styleId;
|
|
1130
|
-
style.textContent = `
|
|
1131
|
-
.playkit-auth-modal {
|
|
1132
|
-
position: fixed;
|
|
1133
|
-
top: 0;
|
|
1134
|
-
left: 0;
|
|
1135
|
-
right: 0;
|
|
1136
|
-
bottom: 0;
|
|
1137
|
-
z-index: 999999;
|
|
1138
|
-
display: flex;
|
|
1139
|
-
justify-content: center;
|
|
1140
|
-
align-items: center;
|
|
1141
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
.playkit-auth-overlay {
|
|
1145
|
-
position: absolute;
|
|
1146
|
-
top: 0;
|
|
1147
|
-
left: 0;
|
|
1148
|
-
right: 0;
|
|
1149
|
-
bottom: 0;
|
|
1150
|
-
background: rgba(0, 0, 0, 0.8);
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
.playkit-auth-container {
|
|
1154
|
-
position: relative;
|
|
1155
|
-
background: #fff;
|
|
1156
|
-
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
1157
|
-
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
|
|
1158
|
-
width: 90%;
|
|
1159
|
-
max-width: 320px;
|
|
1160
|
-
overflow: hidden;
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
.playkit-auth-panel {
|
|
1164
|
-
padding: 24px;
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
.playkit-auth-header {
|
|
1168
|
-
text-align: center;
|
|
1169
|
-
margin-bottom: 20px;
|
|
1170
|
-
position: relative;
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
.playkit-auth-header h2 {
|
|
1174
|
-
margin: 0 0 8px 0;
|
|
1175
|
-
font-size: 14px;
|
|
1176
|
-
font-weight: 600;
|
|
1177
|
-
color: #171717;
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
.playkit-auth-header p {
|
|
1181
|
-
margin: 0;
|
|
1182
|
-
font-size: 14px;
|
|
1183
|
-
color: #666;
|
|
1184
|
-
line-height: 1.5;
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
.playkit-back-button {
|
|
1188
|
-
position: absolute;
|
|
1189
|
-
left: 0;
|
|
1190
|
-
top: 0;
|
|
1191
|
-
background: transparent;
|
|
1192
|
-
border: none;
|
|
1193
|
-
cursor: pointer;
|
|
1194
|
-
padding: 4px;
|
|
1195
|
-
color: #666;
|
|
1196
|
-
transition: background-color 0.2s ease, color 0.2s ease;
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
.playkit-back-button:hover {
|
|
1200
|
-
background: #f5f5f5;
|
|
1201
|
-
color: #171717;
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
.playkit-auth-toggle {
|
|
1205
|
-
display: flex;
|
|
1206
|
-
background: #f5f5f5;
|
|
1207
|
-
padding: 2px;
|
|
1208
|
-
margin-bottom: 20px;
|
|
1209
|
-
gap: 2px;
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
.playkit-toggle-option {
|
|
1213
|
-
flex: 1;
|
|
1214
|
-
display: flex;
|
|
1215
|
-
justify-content: center;
|
|
1216
|
-
align-items: center;
|
|
1217
|
-
padding: 10px 16px;
|
|
1218
|
-
cursor: pointer;
|
|
1219
|
-
transition: background-color 0.2s ease;
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
.playkit-toggle-option input {
|
|
1223
|
-
display: none;
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
.playkit-toggle-option span {
|
|
1227
|
-
font-size: 14px;
|
|
1228
|
-
font-weight: 500;
|
|
1229
|
-
color: #666;
|
|
1230
|
-
transition: color 0.2s ease;
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
.playkit-toggle-option input:checked + span {
|
|
1234
|
-
color: #fff;
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
|
-
.playkit-toggle-option:has(input:checked) {
|
|
1238
|
-
background: #171717;
|
|
1239
|
-
}
|
|
1240
|
-
|
|
1241
|
-
.playkit-auth-input-group {
|
|
1242
|
-
margin-bottom: 20px;
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
|
-
.playkit-input-wrapper {
|
|
1246
|
-
position: relative;
|
|
1247
|
-
display: flex;
|
|
1248
|
-
align-items: center;
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
.playkit-input-icon {
|
|
1252
|
-
position: absolute;
|
|
1253
|
-
left: 12px;
|
|
1254
|
-
color: #999;
|
|
1255
|
-
pointer-events: none;
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
.playkit-input-wrapper input {
|
|
1259
|
-
width: 100%;
|
|
1260
|
-
padding: 10px 12px 10px 44px;
|
|
1261
|
-
border: 1px solid #e5e7eb;
|
|
1262
|
-
font-size: 14px;
|
|
1263
|
-
transition: border-color 0.2s ease;
|
|
1264
|
-
box-sizing: border-box;
|
|
1265
|
-
background: #fff;
|
|
1266
|
-
}
|
|
1267
|
-
|
|
1268
|
-
.playkit-input-wrapper input:hover {
|
|
1269
|
-
border-color: #d4d4d4;
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
|
-
.playkit-input-wrapper input:focus {
|
|
1273
|
-
outline: none;
|
|
1274
|
-
border-color: #171717;
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
.playkit-code-inputs {
|
|
1278
|
-
display: flex;
|
|
1279
|
-
gap: 8px;
|
|
1280
|
-
justify-content: center;
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
.playkit-code-input {
|
|
1284
|
-
width: 40px !important;
|
|
1285
|
-
height: 48px;
|
|
1286
|
-
text-align: center;
|
|
1287
|
-
font-size: 20px;
|
|
1288
|
-
font-weight: 600;
|
|
1289
|
-
border: 1px solid #e5e7eb !important;
|
|
1290
|
-
padding: 0 !important;
|
|
1291
|
-
transition: border-color 0.2s ease;
|
|
1292
|
-
background: #fff;
|
|
1293
|
-
-moz-appearance: textfield;
|
|
1294
|
-
}
|
|
1295
|
-
|
|
1296
|
-
.playkit-code-input::-webkit-outer-spin-button,
|
|
1297
|
-
.playkit-code-input::-webkit-inner-spin-button {
|
|
1298
|
-
-webkit-appearance: none;
|
|
1299
|
-
margin: 0;
|
|
1300
|
-
}
|
|
1301
|
-
|
|
1302
|
-
.playkit-code-input:hover {
|
|
1303
|
-
border-color: #d4d4d4 !important;
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
.playkit-code-input:focus {
|
|
1307
|
-
outline: none;
|
|
1308
|
-
border-color: #171717 !important;
|
|
1309
|
-
}
|
|
1310
|
-
|
|
1311
|
-
.playkit-auth-button {
|
|
1312
|
-
width: 100%;
|
|
1313
|
-
padding: 10px 16px;
|
|
1314
|
-
background: #171717;
|
|
1315
|
-
color: white;
|
|
1316
|
-
border: none;
|
|
1317
|
-
font-size: 14px;
|
|
1318
|
-
font-weight: 500;
|
|
1319
|
-
cursor: pointer;
|
|
1320
|
-
transition: background 0.2s ease;
|
|
1321
|
-
}
|
|
1322
|
-
|
|
1323
|
-
.playkit-auth-button:hover:not(:disabled) {
|
|
1324
|
-
background: #404040;
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
.playkit-auth-button:active:not(:disabled) {
|
|
1328
|
-
background: #0a0a0a;
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
|
-
.playkit-auth-button:disabled {
|
|
1332
|
-
background: #e5e7eb;
|
|
1333
|
-
color: #999;
|
|
1334
|
-
cursor: not-allowed;
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
.playkit-auth-error {
|
|
1338
|
-
margin-top: 16px;
|
|
1339
|
-
padding: 12px 16px;
|
|
1340
|
-
background: #fef2f2;
|
|
1341
|
-
border: 1px solid #fecaca;
|
|
1342
|
-
color: #dc2626;
|
|
1343
|
-
font-size: 13px;
|
|
1344
|
-
text-align: left;
|
|
1345
|
-
display: none;
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
|
-
.playkit-auth-error.show {
|
|
1349
|
-
display: block;
|
|
1350
|
-
}
|
|
1351
|
-
|
|
1352
|
-
.playkit-loading-overlay {
|
|
1353
|
-
position: absolute;
|
|
1354
|
-
top: 0;
|
|
1355
|
-
left: 0;
|
|
1356
|
-
right: 0;
|
|
1357
|
-
bottom: 0;
|
|
1358
|
-
background: rgba(255, 255, 255, 0.96);
|
|
1359
|
-
display: flex;
|
|
1360
|
-
justify-content: center;
|
|
1361
|
-
align-items: center;
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
.playkit-spinner {
|
|
1365
|
-
width: 24px;
|
|
1366
|
-
height: 24px;
|
|
1367
|
-
border: 2px solid #e5e7eb;
|
|
1368
|
-
border-top: 2px solid #171717;
|
|
1369
|
-
border-radius: 50%;
|
|
1370
|
-
animation: playkit-spin 1s linear infinite;
|
|
1371
|
-
}
|
|
1372
|
-
|
|
1373
|
-
@keyframes playkit-spin {
|
|
1374
|
-
0% { transform: rotate(0deg); }
|
|
1375
|
-
100% { transform: rotate(360deg); }
|
|
1376
|
-
}
|
|
1377
|
-
|
|
1378
|
-
@media (max-width: 480px) {
|
|
1379
|
-
.playkit-auth-container {
|
|
1380
|
-
width: 95%;
|
|
1381
|
-
max-width: none;
|
|
1382
|
-
}
|
|
1383
|
-
|
|
1384
|
-
.playkit-auth-panel {
|
|
1385
|
-
padding: 20px;
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
|
-
.playkit-code-input {
|
|
1389
|
-
width: 36px !important;
|
|
1390
|
-
height: 44px;
|
|
1391
|
-
font-size: 18px;
|
|
1392
|
-
}
|
|
1393
|
-
|
|
1394
|
-
.playkit-code-inputs {
|
|
1395
|
-
gap: 6px;
|
|
1396
|
-
}
|
|
1397
|
-
}
|
|
1139
|
+
style.textContent = `
|
|
1140
|
+
.playkit-auth-modal {
|
|
1141
|
+
position: fixed;
|
|
1142
|
+
top: 0;
|
|
1143
|
+
left: 0;
|
|
1144
|
+
right: 0;
|
|
1145
|
+
bottom: 0;
|
|
1146
|
+
z-index: 999999;
|
|
1147
|
+
display: flex;
|
|
1148
|
+
justify-content: center;
|
|
1149
|
+
align-items: center;
|
|
1150
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
.playkit-auth-overlay {
|
|
1154
|
+
position: absolute;
|
|
1155
|
+
top: 0;
|
|
1156
|
+
left: 0;
|
|
1157
|
+
right: 0;
|
|
1158
|
+
bottom: 0;
|
|
1159
|
+
background: rgba(0, 0, 0, 0.8);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
.playkit-auth-container {
|
|
1163
|
+
position: relative;
|
|
1164
|
+
background: #fff;
|
|
1165
|
+
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
1166
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
|
|
1167
|
+
width: 90%;
|
|
1168
|
+
max-width: 320px;
|
|
1169
|
+
overflow: hidden;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
.playkit-auth-panel {
|
|
1173
|
+
padding: 24px;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
.playkit-auth-header {
|
|
1177
|
+
text-align: center;
|
|
1178
|
+
margin-bottom: 20px;
|
|
1179
|
+
position: relative;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
.playkit-auth-header h2 {
|
|
1183
|
+
margin: 0 0 8px 0;
|
|
1184
|
+
font-size: 14px;
|
|
1185
|
+
font-weight: 600;
|
|
1186
|
+
color: #171717;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
.playkit-auth-header p {
|
|
1190
|
+
margin: 0;
|
|
1191
|
+
font-size: 14px;
|
|
1192
|
+
color: #666;
|
|
1193
|
+
line-height: 1.5;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
.playkit-back-button {
|
|
1197
|
+
position: absolute;
|
|
1198
|
+
left: 0;
|
|
1199
|
+
top: 0;
|
|
1200
|
+
background: transparent;
|
|
1201
|
+
border: none;
|
|
1202
|
+
cursor: pointer;
|
|
1203
|
+
padding: 4px;
|
|
1204
|
+
color: #666;
|
|
1205
|
+
transition: background-color 0.2s ease, color 0.2s ease;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
.playkit-back-button:hover {
|
|
1209
|
+
background: #f5f5f5;
|
|
1210
|
+
color: #171717;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
.playkit-auth-toggle {
|
|
1214
|
+
display: flex;
|
|
1215
|
+
background: #f5f5f5;
|
|
1216
|
+
padding: 2px;
|
|
1217
|
+
margin-bottom: 20px;
|
|
1218
|
+
gap: 2px;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
.playkit-toggle-option {
|
|
1222
|
+
flex: 1;
|
|
1223
|
+
display: flex;
|
|
1224
|
+
justify-content: center;
|
|
1225
|
+
align-items: center;
|
|
1226
|
+
padding: 10px 16px;
|
|
1227
|
+
cursor: pointer;
|
|
1228
|
+
transition: background-color 0.2s ease;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
.playkit-toggle-option input {
|
|
1232
|
+
display: none;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
.playkit-toggle-option span {
|
|
1236
|
+
font-size: 14px;
|
|
1237
|
+
font-weight: 500;
|
|
1238
|
+
color: #666;
|
|
1239
|
+
transition: color 0.2s ease;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
.playkit-toggle-option input:checked + span {
|
|
1243
|
+
color: #fff;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
.playkit-toggle-option:has(input:checked) {
|
|
1247
|
+
background: #171717;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
.playkit-auth-input-group {
|
|
1251
|
+
margin-bottom: 20px;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
.playkit-input-wrapper {
|
|
1255
|
+
position: relative;
|
|
1256
|
+
display: flex;
|
|
1257
|
+
align-items: center;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
.playkit-input-icon {
|
|
1261
|
+
position: absolute;
|
|
1262
|
+
left: 12px;
|
|
1263
|
+
color: #999;
|
|
1264
|
+
pointer-events: none;
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
.playkit-input-wrapper input {
|
|
1268
|
+
width: 100%;
|
|
1269
|
+
padding: 10px 12px 10px 44px;
|
|
1270
|
+
border: 1px solid #e5e7eb;
|
|
1271
|
+
font-size: 14px;
|
|
1272
|
+
transition: border-color 0.2s ease;
|
|
1273
|
+
box-sizing: border-box;
|
|
1274
|
+
background: #fff;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
.playkit-input-wrapper input:hover {
|
|
1278
|
+
border-color: #d4d4d4;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
.playkit-input-wrapper input:focus {
|
|
1282
|
+
outline: none;
|
|
1283
|
+
border-color: #171717;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
.playkit-code-inputs {
|
|
1287
|
+
display: flex;
|
|
1288
|
+
gap: 8px;
|
|
1289
|
+
justify-content: center;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
.playkit-code-input {
|
|
1293
|
+
width: 40px !important;
|
|
1294
|
+
height: 48px;
|
|
1295
|
+
text-align: center;
|
|
1296
|
+
font-size: 20px;
|
|
1297
|
+
font-weight: 600;
|
|
1298
|
+
border: 1px solid #e5e7eb !important;
|
|
1299
|
+
padding: 0 !important;
|
|
1300
|
+
transition: border-color 0.2s ease;
|
|
1301
|
+
background: #fff;
|
|
1302
|
+
-moz-appearance: textfield;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
.playkit-code-input::-webkit-outer-spin-button,
|
|
1306
|
+
.playkit-code-input::-webkit-inner-spin-button {
|
|
1307
|
+
-webkit-appearance: none;
|
|
1308
|
+
margin: 0;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
.playkit-code-input:hover {
|
|
1312
|
+
border-color: #d4d4d4 !important;
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
.playkit-code-input:focus {
|
|
1316
|
+
outline: none;
|
|
1317
|
+
border-color: #171717 !important;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
.playkit-auth-button {
|
|
1321
|
+
width: 100%;
|
|
1322
|
+
padding: 10px 16px;
|
|
1323
|
+
background: #171717;
|
|
1324
|
+
color: white;
|
|
1325
|
+
border: none;
|
|
1326
|
+
font-size: 14px;
|
|
1327
|
+
font-weight: 500;
|
|
1328
|
+
cursor: pointer;
|
|
1329
|
+
transition: background 0.2s ease;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
.playkit-auth-button:hover:not(:disabled) {
|
|
1333
|
+
background: #404040;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
.playkit-auth-button:active:not(:disabled) {
|
|
1337
|
+
background: #0a0a0a;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
.playkit-auth-button:disabled {
|
|
1341
|
+
background: #e5e7eb;
|
|
1342
|
+
color: #999;
|
|
1343
|
+
cursor: not-allowed;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
.playkit-auth-error {
|
|
1347
|
+
margin-top: 16px;
|
|
1348
|
+
padding: 12px 16px;
|
|
1349
|
+
background: #fef2f2;
|
|
1350
|
+
border: 1px solid #fecaca;
|
|
1351
|
+
color: #dc2626;
|
|
1352
|
+
font-size: 13px;
|
|
1353
|
+
text-align: left;
|
|
1354
|
+
display: none;
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
.playkit-auth-error.show {
|
|
1358
|
+
display: block;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
.playkit-loading-overlay {
|
|
1362
|
+
position: absolute;
|
|
1363
|
+
top: 0;
|
|
1364
|
+
left: 0;
|
|
1365
|
+
right: 0;
|
|
1366
|
+
bottom: 0;
|
|
1367
|
+
background: rgba(255, 255, 255, 0.96);
|
|
1368
|
+
display: flex;
|
|
1369
|
+
justify-content: center;
|
|
1370
|
+
align-items: center;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
.playkit-spinner {
|
|
1374
|
+
width: 24px;
|
|
1375
|
+
height: 24px;
|
|
1376
|
+
border: 2px solid #e5e7eb;
|
|
1377
|
+
border-top: 2px solid #171717;
|
|
1378
|
+
border-radius: 50%;
|
|
1379
|
+
animation: playkit-spin 1s linear infinite;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
@keyframes playkit-spin {
|
|
1383
|
+
0% { transform: rotate(0deg); }
|
|
1384
|
+
100% { transform: rotate(360deg); }
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
@media (max-width: 480px) {
|
|
1388
|
+
.playkit-auth-container {
|
|
1389
|
+
width: 95%;
|
|
1390
|
+
max-width: none;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
.playkit-auth-panel {
|
|
1394
|
+
padding: 20px;
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
.playkit-code-input {
|
|
1398
|
+
width: 36px !important;
|
|
1399
|
+
height: 44px;
|
|
1400
|
+
font-size: 18px;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
.playkit-code-inputs {
|
|
1404
|
+
gap: 6px;
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1398
1407
|
`;
|
|
1399
1408
|
document.head.appendChild(style);
|
|
1400
1409
|
}
|
|
@@ -1415,14 +1424,14 @@ class AuthFlowManager extends EventEmitter {
|
|
|
1415
1424
|
: this.t('phonePlaceholder');
|
|
1416
1425
|
// Update icon
|
|
1417
1426
|
if (isEmail) {
|
|
1418
|
-
identifierIcon.innerHTML = `
|
|
1419
|
-
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
|
|
1420
|
-
<polyline points="22,6 12,13 2,6"></polyline>
|
|
1427
|
+
identifierIcon.innerHTML = `
|
|
1428
|
+
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
|
|
1429
|
+
<polyline points="22,6 12,13 2,6"></polyline>
|
|
1421
1430
|
`;
|
|
1422
1431
|
}
|
|
1423
1432
|
else {
|
|
1424
|
-
identifierIcon.innerHTML = `
|
|
1425
|
-
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path>
|
|
1433
|
+
identifierIcon.innerHTML = `
|
|
1434
|
+
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path>
|
|
1426
1435
|
`;
|
|
1427
1436
|
}
|
|
1428
1437
|
};
|
|
@@ -1443,7 +1452,7 @@ class AuthFlowManager extends EventEmitter {
|
|
|
1443
1452
|
this.otpInstance = new window.VanillaOTP(codeInputsContainer);
|
|
1444
1453
|
// Auto-submit when all 6 digits entered
|
|
1445
1454
|
const codeInputs = (_d = this.modal) === null || _d === void 0 ? void 0 : _d.querySelectorAll('.playkit-code-input');
|
|
1446
|
-
codeInputs === null || codeInputs === void 0 ? void 0 : codeInputs.forEach((input,
|
|
1455
|
+
codeInputs === null || codeInputs === void 0 ? void 0 : codeInputs.forEach((input, _index) => {
|
|
1447
1456
|
input.addEventListener('input', () => {
|
|
1448
1457
|
// Check if all inputs are filled
|
|
1449
1458
|
const allFilled = Array.from(codeInputs).every(inp => inp.value.length === 1);
|
|
@@ -1535,7 +1544,7 @@ class AuthFlowManager extends EventEmitter {
|
|
|
1535
1544
|
async sendVerificationCode(identifier, type) {
|
|
1536
1545
|
const response = await fetch(`${this.baseURL}/api/auth/send-code`, {
|
|
1537
1546
|
method: 'POST',
|
|
1538
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1547
|
+
headers: Object.assign({ 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
1539
1548
|
body: JSON.stringify({ identifier, type }),
|
|
1540
1549
|
});
|
|
1541
1550
|
if (!response.ok) {
|
|
@@ -1557,7 +1566,7 @@ class AuthFlowManager extends EventEmitter {
|
|
|
1557
1566
|
}
|
|
1558
1567
|
const response = await fetch(`${this.baseURL}/api/auth/verify-code`, {
|
|
1559
1568
|
method: 'POST',
|
|
1560
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1569
|
+
headers: Object.assign({ 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
1561
1570
|
body: JSON.stringify({
|
|
1562
1571
|
sessionId: this.currentSessionId,
|
|
1563
1572
|
code,
|
|
@@ -1578,7 +1587,9 @@ class AuthFlowManager extends EventEmitter {
|
|
|
1578
1587
|
async setDefaultAuthTypeByRegion() {
|
|
1579
1588
|
var _a;
|
|
1580
1589
|
try {
|
|
1581
|
-
const response = await fetch(`${this.baseURL}/api/reachability
|
|
1590
|
+
const response = await fetch(`${this.baseURL}/api/reachability`, {
|
|
1591
|
+
headers: Object.assign({}, getSDKHeaders()),
|
|
1592
|
+
});
|
|
1582
1593
|
if (response.ok) {
|
|
1583
1594
|
const data = await response.json();
|
|
1584
1595
|
if (data.region === 'CN') {
|
|
@@ -1835,76 +1846,76 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
1835
1846
|
// Create modal overlay - dark bg-black/80 style
|
|
1836
1847
|
const overlay = document.createElement('div');
|
|
1837
1848
|
overlay.id = 'playkit-login-modal';
|
|
1838
|
-
overlay.style.cssText = `
|
|
1839
|
-
position: fixed;
|
|
1840
|
-
top: 0;
|
|
1841
|
-
left: 0;
|
|
1842
|
-
right: 0;
|
|
1843
|
-
bottom: 0;
|
|
1844
|
-
background: rgba(0, 0, 0, 0.8);
|
|
1845
|
-
display: flex;
|
|
1846
|
-
align-items: center;
|
|
1847
|
-
justify-content: center;
|
|
1848
|
-
z-index: 999999;
|
|
1849
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
1849
|
+
overlay.style.cssText = `
|
|
1850
|
+
position: fixed;
|
|
1851
|
+
top: 0;
|
|
1852
|
+
left: 0;
|
|
1853
|
+
right: 0;
|
|
1854
|
+
bottom: 0;
|
|
1855
|
+
background: rgba(0, 0, 0, 0.8);
|
|
1856
|
+
display: flex;
|
|
1857
|
+
align-items: center;
|
|
1858
|
+
justify-content: center;
|
|
1859
|
+
z-index: 999999;
|
|
1860
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
1850
1861
|
`;
|
|
1851
1862
|
// Create modal card - square corners, shadow-xl style
|
|
1852
1863
|
const card = document.createElement('div');
|
|
1853
|
-
card.style.cssText = `
|
|
1854
|
-
background: #fff;
|
|
1855
|
-
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
1856
|
-
padding: 24px;
|
|
1857
|
-
max-width: 320px;
|
|
1858
|
-
width: 90%;
|
|
1859
|
-
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
|
|
1860
|
-
text-align: center;
|
|
1864
|
+
card.style.cssText = `
|
|
1865
|
+
background: #fff;
|
|
1866
|
+
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
1867
|
+
padding: 24px;
|
|
1868
|
+
max-width: 320px;
|
|
1869
|
+
width: 90%;
|
|
1870
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
|
|
1871
|
+
text-align: center;
|
|
1861
1872
|
`;
|
|
1862
1873
|
// Subtitle / status text
|
|
1863
1874
|
const subtitle = document.createElement('p');
|
|
1864
1875
|
subtitle.id = 'playkit-modal-subtitle';
|
|
1865
1876
|
subtitle.textContent = this.t('loginWithPlayKit');
|
|
1866
|
-
subtitle.style.cssText = `
|
|
1867
|
-
margin: 0 0 20px;
|
|
1868
|
-
font-size: 14px;
|
|
1869
|
-
color: #666;
|
|
1877
|
+
subtitle.style.cssText = `
|
|
1878
|
+
margin: 0 0 20px;
|
|
1879
|
+
font-size: 14px;
|
|
1880
|
+
color: #666;
|
|
1870
1881
|
`;
|
|
1871
1882
|
card.appendChild(subtitle);
|
|
1872
1883
|
// Loading spinner (hidden initially)
|
|
1873
1884
|
const spinner = document.createElement('div');
|
|
1874
1885
|
spinner.id = 'playkit-modal-spinner';
|
|
1875
|
-
spinner.style.cssText = `
|
|
1876
|
-
display: none;
|
|
1877
|
-
width: 24px;
|
|
1878
|
-
height: 24px;
|
|
1879
|
-
margin: 0 auto 16px;
|
|
1880
|
-
border: 2px solid #e5e7eb;
|
|
1881
|
-
border-top-color: #171717;
|
|
1882
|
-
border-radius: 50%;
|
|
1883
|
-
animation: playkit-spin 1s linear infinite;
|
|
1886
|
+
spinner.style.cssText = `
|
|
1887
|
+
display: none;
|
|
1888
|
+
width: 24px;
|
|
1889
|
+
height: 24px;
|
|
1890
|
+
margin: 0 auto 16px;
|
|
1891
|
+
border: 2px solid #e5e7eb;
|
|
1892
|
+
border-top-color: #171717;
|
|
1893
|
+
border-radius: 50%;
|
|
1894
|
+
animation: playkit-spin 1s linear infinite;
|
|
1884
1895
|
`;
|
|
1885
1896
|
card.appendChild(spinner);
|
|
1886
1897
|
// Add keyframes for spinner
|
|
1887
1898
|
const style = document.createElement('style');
|
|
1888
|
-
style.textContent = `
|
|
1889
|
-
@keyframes playkit-spin {
|
|
1890
|
-
to { transform: rotate(360deg); }
|
|
1891
|
-
}
|
|
1899
|
+
style.textContent = `
|
|
1900
|
+
@keyframes playkit-spin {
|
|
1901
|
+
to { transform: rotate(360deg); }
|
|
1902
|
+
}
|
|
1892
1903
|
`;
|
|
1893
1904
|
document.head.appendChild(style);
|
|
1894
1905
|
// Login button - square corners, simple dark style
|
|
1895
1906
|
const loginBtn = document.createElement('button');
|
|
1896
1907
|
loginBtn.id = 'playkit-modal-login-btn';
|
|
1897
1908
|
loginBtn.textContent = this.t('loginToPlay');
|
|
1898
|
-
loginBtn.style.cssText = `
|
|
1899
|
-
width: 100%;
|
|
1900
|
-
padding: 10px 16px;
|
|
1901
|
-
font-size: 14px;
|
|
1902
|
-
font-weight: 500;
|
|
1903
|
-
color: white;
|
|
1904
|
-
background: #171717;
|
|
1905
|
-
border: none;
|
|
1906
|
-
cursor: pointer;
|
|
1907
|
-
transition: background 0.2s ease;
|
|
1909
|
+
loginBtn.style.cssText = `
|
|
1910
|
+
width: 100%;
|
|
1911
|
+
padding: 10px 16px;
|
|
1912
|
+
font-size: 14px;
|
|
1913
|
+
font-weight: 500;
|
|
1914
|
+
color: white;
|
|
1915
|
+
background: #171717;
|
|
1916
|
+
border: none;
|
|
1917
|
+
cursor: pointer;
|
|
1918
|
+
transition: background 0.2s ease;
|
|
1908
1919
|
`;
|
|
1909
1920
|
loginBtn.onmouseenter = () => {
|
|
1910
1921
|
loginBtn.style.background = '#404040';
|
|
@@ -1931,18 +1942,18 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
1931
1942
|
const cancelBtn = document.createElement('button');
|
|
1932
1943
|
cancelBtn.id = 'playkit-modal-cancel-btn';
|
|
1933
1944
|
cancelBtn.textContent = this.t('cancel');
|
|
1934
|
-
cancelBtn.style.cssText = `
|
|
1935
|
-
display: none;
|
|
1936
|
-
width: 100%;
|
|
1937
|
-
margin-top: 8px;
|
|
1938
|
-
padding: 10px 16px;
|
|
1939
|
-
font-size: 14px;
|
|
1940
|
-
font-weight: 500;
|
|
1941
|
-
color: #666;
|
|
1942
|
-
background: transparent;
|
|
1943
|
-
border: 1px solid #e5e7eb;
|
|
1944
|
-
cursor: pointer;
|
|
1945
|
-
transition: all 0.2s ease;
|
|
1945
|
+
cancelBtn.style.cssText = `
|
|
1946
|
+
display: none;
|
|
1947
|
+
width: 100%;
|
|
1948
|
+
margin-top: 8px;
|
|
1949
|
+
padding: 10px 16px;
|
|
1950
|
+
font-size: 14px;
|
|
1951
|
+
font-weight: 500;
|
|
1952
|
+
color: #666;
|
|
1953
|
+
background: transparent;
|
|
1954
|
+
border: 1px solid #e5e7eb;
|
|
1955
|
+
cursor: pointer;
|
|
1956
|
+
transition: all 0.2s ease;
|
|
1946
1957
|
`;
|
|
1947
1958
|
cancelBtn.onmouseenter = () => {
|
|
1948
1959
|
cancelBtn.style.background = '#f5f5f5';
|
|
@@ -2012,11 +2023,11 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
2012
2023
|
// Create error title
|
|
2013
2024
|
const errorTitle = document.createElement('h3');
|
|
2014
2025
|
errorTitle.textContent = this.t(titleKey);
|
|
2015
|
-
errorTitle.style.cssText = `
|
|
2016
|
-
margin: 0 0 8px;
|
|
2017
|
-
font-size: 14px;
|
|
2018
|
-
font-weight: 600;
|
|
2019
|
-
color: ${iconColor};
|
|
2026
|
+
errorTitle.style.cssText = `
|
|
2027
|
+
margin: 0 0 8px;
|
|
2028
|
+
font-size: 14px;
|
|
2029
|
+
font-weight: 600;
|
|
2030
|
+
color: ${iconColor};
|
|
2020
2031
|
`;
|
|
2021
2032
|
// Update subtitle with error description
|
|
2022
2033
|
subtitle.textContent = this.t(descKey);
|
|
@@ -2086,9 +2097,7 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
2086
2097
|
// Step 1: Initiate device auth session
|
|
2087
2098
|
const initResponse = await fetch(`${this.baseURL}/api/device-auth/initiate`, {
|
|
2088
2099
|
method: 'POST',
|
|
2089
|
-
headers: {
|
|
2090
|
-
'Content-Type': 'application/json',
|
|
2091
|
-
},
|
|
2100
|
+
headers: Object.assign({ 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
2092
2101
|
body: JSON.stringify({
|
|
2093
2102
|
game_id: this.gameId,
|
|
2094
2103
|
code_challenge: codeChallenge,
|
|
@@ -2157,7 +2166,7 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
2157
2166
|
return;
|
|
2158
2167
|
}
|
|
2159
2168
|
try {
|
|
2160
|
-
const pollResponse = await fetch(`${this.baseURL}/api/device-auth/poll?session_id=${encodeURIComponent(session_id)}&code_verifier=${encodeURIComponent(codeVerifier)}
|
|
2169
|
+
const pollResponse = await fetch(`${this.baseURL}/api/device-auth/poll?session_id=${encodeURIComponent(session_id)}&code_verifier=${encodeURIComponent(codeVerifier)}`, { headers: Object.assign({}, getSDKHeaders()) });
|
|
2161
2170
|
const pollData = await pollResponse.json();
|
|
2162
2171
|
if (pollResponse.ok) {
|
|
2163
2172
|
if (pollData.status === 'pending') {
|
|
@@ -2285,9 +2294,7 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
2285
2294
|
// Initiate device auth session
|
|
2286
2295
|
const initResponse = await fetch(`${this.baseURL}/api/device-auth/initiate`, {
|
|
2287
2296
|
method: 'POST',
|
|
2288
|
-
headers: {
|
|
2289
|
-
'Content-Type': 'application/json',
|
|
2290
|
-
},
|
|
2297
|
+
headers: Object.assign({ 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
2291
2298
|
body: JSON.stringify({
|
|
2292
2299
|
game_id: this.gameId,
|
|
2293
2300
|
code_challenge: codeChallenge,
|
|
@@ -2350,7 +2357,7 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
2350
2357
|
return;
|
|
2351
2358
|
}
|
|
2352
2359
|
try {
|
|
2353
|
-
const pollResponse = await fetch(`${this.baseURL}/api/device-auth/poll?session_id=${encodeURIComponent(sessionId)}&code_verifier=${encodeURIComponent(codeVerifier)}
|
|
2360
|
+
const pollResponse = await fetch(`${this.baseURL}/api/device-auth/poll?session_id=${encodeURIComponent(sessionId)}&code_verifier=${encodeURIComponent(codeVerifier)}`, { headers: Object.assign({}, getSDKHeaders()) });
|
|
2354
2361
|
const pollData = await pollResponse.json();
|
|
2355
2362
|
if (pollResponse.ok) {
|
|
2356
2363
|
if (pollData.status === 'pending') {
|
|
@@ -2419,7 +2426,7 @@ DeviceAuthFlowManager.activeInstance = null;
|
|
|
2419
2426
|
* Handles JWT exchange and token management
|
|
2420
2427
|
*/
|
|
2421
2428
|
// @ts-ignore - replaced at build time
|
|
2422
|
-
const DEFAULT_BASE_URL$
|
|
2429
|
+
const DEFAULT_BASE_URL$6 = "https://api.playkit.ai";
|
|
2423
2430
|
const JWT_EXCHANGE_ENDPOINT = '/api/external/exchange-jwt';
|
|
2424
2431
|
const TOKEN_REFRESH_ENDPOINT = '/api/auth/refresh';
|
|
2425
2432
|
class AuthManager extends EventEmitter {
|
|
@@ -2437,7 +2444,7 @@ class AuthManager extends EventEmitter {
|
|
|
2437
2444
|
this.storage = new TokenStorage({
|
|
2438
2445
|
mode: config.mode === 'server' ? 'server' : 'browser',
|
|
2439
2446
|
});
|
|
2440
|
-
this.baseURL = config.baseURL || DEFAULT_BASE_URL$
|
|
2447
|
+
this.baseURL = config.baseURL || DEFAULT_BASE_URL$6;
|
|
2441
2448
|
this.authState = {
|
|
2442
2449
|
isAuthenticated: false,
|
|
2443
2450
|
};
|
|
@@ -2619,10 +2626,7 @@ class AuthManager extends EventEmitter {
|
|
|
2619
2626
|
try {
|
|
2620
2627
|
const response = await fetch(`${this.baseURL}${JWT_EXCHANGE_ENDPOINT}`, {
|
|
2621
2628
|
method: 'POST',
|
|
2622
|
-
headers: {
|
|
2623
|
-
Authorization: `Bearer ${jwt}`,
|
|
2624
|
-
'Content-Type': 'application/json',
|
|
2625
|
-
},
|
|
2629
|
+
headers: Object.assign({ Authorization: `Bearer ${jwt}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
2626
2630
|
body: JSON.stringify({ gameId: this.config.gameId }),
|
|
2627
2631
|
});
|
|
2628
2632
|
if (!response.ok) {
|
|
@@ -2972,9 +2976,7 @@ class AuthManager extends EventEmitter {
|
|
|
2972
2976
|
this.logger.debug('Refreshing access token');
|
|
2973
2977
|
const response = await fetch(`${this.baseURL}${TOKEN_REFRESH_ENDPOINT}`, {
|
|
2974
2978
|
method: 'POST',
|
|
2975
|
-
headers: {
|
|
2976
|
-
'Content-Type': 'application/json',
|
|
2977
|
-
},
|
|
2979
|
+
headers: Object.assign({ 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
2978
2980
|
body: JSON.stringify({
|
|
2979
2981
|
refresh_token: this.authState.refreshToken,
|
|
2980
2982
|
}),
|
|
@@ -3077,7 +3079,7 @@ const translations = {
|
|
|
3077
3079
|
* RechargeManager handles the recharge modal UI and recharge window opening
|
|
3078
3080
|
*/
|
|
3079
3081
|
class RechargeManager extends EventEmitter {
|
|
3080
|
-
constructor(playerToken, rechargePortalUrl = 'https://playkit.ai/recharge', gameId) {
|
|
3082
|
+
constructor(playerToken, rechargePortalUrl = 'https://players.playkit.ai/recharge', gameId) {
|
|
3081
3083
|
super();
|
|
3082
3084
|
this.modalContainer = null;
|
|
3083
3085
|
this.styleElement = null;
|
|
@@ -3180,220 +3182,220 @@ class RechargeManager extends EventEmitter {
|
|
|
3180
3182
|
return;
|
|
3181
3183
|
}
|
|
3182
3184
|
this.styleElement = document.createElement('style');
|
|
3183
|
-
this.styleElement.textContent = `
|
|
3184
|
-
.playkit-recharge-overlay {
|
|
3185
|
-
position: fixed;
|
|
3186
|
-
top: 0;
|
|
3187
|
-
left: 0;
|
|
3188
|
-
right: 0;
|
|
3189
|
-
bottom: 0;
|
|
3190
|
-
background: rgba(0, 0, 0, 0.8);
|
|
3191
|
-
display: flex;
|
|
3192
|
-
justify-content: center;
|
|
3193
|
-
align-items: center;
|
|
3194
|
-
z-index: 999999;
|
|
3195
|
-
animation: playkit-recharge-fadeIn 0.2s ease-out;
|
|
3196
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3197
|
-
}
|
|
3198
|
-
|
|
3199
|
-
@keyframes playkit-recharge-fadeIn {
|
|
3200
|
-
from {
|
|
3201
|
-
opacity: 0;
|
|
3202
|
-
}
|
|
3203
|
-
to {
|
|
3204
|
-
opacity: 1;
|
|
3205
|
-
}
|
|
3206
|
-
}
|
|
3207
|
-
|
|
3208
|
-
.playkit-recharge-modal {
|
|
3209
|
-
background: #fff;
|
|
3210
|
-
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
3211
|
-
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
|
|
3212
|
-
padding: 24px;
|
|
3213
|
-
max-width: 320px;
|
|
3214
|
-
width: 90%;
|
|
3215
|
-
position: relative;
|
|
3216
|
-
text-align: center;
|
|
3217
|
-
}
|
|
3218
|
-
|
|
3219
|
-
.playkit-recharge-title {
|
|
3220
|
-
font-size: 14px;
|
|
3221
|
-
font-weight: 600;
|
|
3222
|
-
color: #171717;
|
|
3223
|
-
margin: 0 0 8px 0;
|
|
3224
|
-
text-align: center;
|
|
3225
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3226
|
-
}
|
|
3227
|
-
|
|
3228
|
-
.playkit-recharge-message {
|
|
3229
|
-
font-size: 14px;
|
|
3230
|
-
color: #666;
|
|
3231
|
-
margin: 0 0 20px 0;
|
|
3232
|
-
text-align: center;
|
|
3233
|
-
line-height: 1.5;
|
|
3234
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3235
|
-
}
|
|
3236
|
-
|
|
3237
|
-
.playkit-recharge-balance {
|
|
3238
|
-
background: #f5f5f5;
|
|
3239
|
-
border: 1px solid #e5e7eb;
|
|
3240
|
-
padding: 16px;
|
|
3241
|
-
margin: 0 0 20px 0;
|
|
3242
|
-
text-align: center;
|
|
3243
|
-
}
|
|
3244
|
-
|
|
3245
|
-
.playkit-recharge-balance-label {
|
|
3246
|
-
font-size: 12px;
|
|
3247
|
-
color: #666;
|
|
3248
|
-
margin: 0 0 8px 0;
|
|
3249
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3250
|
-
}
|
|
3251
|
-
|
|
3252
|
-
.playkit-recharge-balance-value {
|
|
3253
|
-
font-size: 24px;
|
|
3254
|
-
font-weight: bold;
|
|
3255
|
-
color: #171717;
|
|
3256
|
-
margin: 0;
|
|
3257
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3258
|
-
}
|
|
3259
|
-
|
|
3260
|
-
.playkit-recharge-balance-unit {
|
|
3261
|
-
font-size: 14px;
|
|
3262
|
-
color: #666;
|
|
3263
|
-
margin-left: 4px;
|
|
3264
|
-
}
|
|
3265
|
-
|
|
3266
|
-
.playkit-recharge-buttons {
|
|
3267
|
-
display: flex;
|
|
3268
|
-
flex-direction: column;
|
|
3269
|
-
gap: 8px;
|
|
3270
|
-
}
|
|
3271
|
-
|
|
3272
|
-
.playkit-recharge-button {
|
|
3273
|
-
width: 100%;
|
|
3274
|
-
padding: 10px 16px;
|
|
3275
|
-
border: none;
|
|
3276
|
-
font-size: 14px;
|
|
3277
|
-
font-weight: 500;
|
|
3278
|
-
cursor: pointer;
|
|
3279
|
-
transition: all 0.2s ease;
|
|
3280
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3281
|
-
}
|
|
3282
|
-
|
|
3283
|
-
.playkit-recharge-button-primary {
|
|
3284
|
-
background: #171717;
|
|
3285
|
-
color: white;
|
|
3286
|
-
}
|
|
3287
|
-
|
|
3288
|
-
.playkit-recharge-button-primary:hover {
|
|
3289
|
-
background: #404040;
|
|
3290
|
-
}
|
|
3291
|
-
|
|
3292
|
-
.playkit-recharge-button-primary:active {
|
|
3293
|
-
background: #0a0a0a;
|
|
3294
|
-
}
|
|
3295
|
-
|
|
3296
|
-
.playkit-recharge-button-secondary {
|
|
3297
|
-
background: transparent;
|
|
3298
|
-
color: #666;
|
|
3299
|
-
border: 1px solid #e5e7eb;
|
|
3300
|
-
}
|
|
3301
|
-
|
|
3302
|
-
.playkit-recharge-button-secondary:hover {
|
|
3303
|
-
background: #f5f5f5;
|
|
3304
|
-
border-color: #d4d4d4;
|
|
3305
|
-
}
|
|
3306
|
-
|
|
3307
|
-
.playkit-recharge-button-secondary:active {
|
|
3308
|
-
background: #e5e5e5;
|
|
3309
|
-
}
|
|
3310
|
-
|
|
3311
|
-
@media (max-width: 480px) {
|
|
3312
|
-
.playkit-recharge-modal {
|
|
3313
|
-
padding: 20px;
|
|
3314
|
-
}
|
|
3315
|
-
}
|
|
3316
|
-
|
|
3317
|
-
/* Daily Refresh Toast Styles */
|
|
3318
|
-
.playkit-daily-refresh-toast {
|
|
3319
|
-
position: fixed;
|
|
3320
|
-
top: 20px;
|
|
3321
|
-
right: 20px;
|
|
3322
|
-
background: #fff;
|
|
3323
|
-
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
3324
|
-
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.1);
|
|
3325
|
-
padding: 16px 20px;
|
|
3326
|
-
min-width: 240px;
|
|
3327
|
-
max-width: 320px;
|
|
3328
|
-
z-index: 999998;
|
|
3329
|
-
animation: playkit-toast-slideIn 0.3s ease-out;
|
|
3330
|
-
display: flex;
|
|
3331
|
-
align-items: flex-start;
|
|
3332
|
-
gap: 12px;
|
|
3333
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3334
|
-
}
|
|
3335
|
-
|
|
3336
|
-
.playkit-daily-refresh-toast.hiding {
|
|
3337
|
-
animation: playkit-toast-fadeOut 0.3s ease-out forwards;
|
|
3338
|
-
}
|
|
3339
|
-
|
|
3340
|
-
@keyframes playkit-toast-slideIn {
|
|
3341
|
-
from {
|
|
3342
|
-
transform: translateX(100%);
|
|
3343
|
-
opacity: 0;
|
|
3344
|
-
}
|
|
3345
|
-
to {
|
|
3346
|
-
transform: translateX(0);
|
|
3347
|
-
opacity: 1;
|
|
3348
|
-
}
|
|
3349
|
-
}
|
|
3350
|
-
|
|
3351
|
-
@keyframes playkit-toast-fadeOut {
|
|
3352
|
-
from {
|
|
3353
|
-
transform: translateX(0);
|
|
3354
|
-
opacity: 1;
|
|
3355
|
-
}
|
|
3356
|
-
to {
|
|
3357
|
-
transform: translateX(100%);
|
|
3358
|
-
opacity: 0;
|
|
3359
|
-
}
|
|
3360
|
-
}
|
|
3361
|
-
|
|
3362
|
-
.playkit-toast-icon {
|
|
3363
|
-
width: 24px;
|
|
3364
|
-
height: 24px;
|
|
3365
|
-
background: #171717;
|
|
3366
|
-
border-radius: 50%;
|
|
3367
|
-
display: flex;
|
|
3368
|
-
align-items: center;
|
|
3369
|
-
justify-content: center;
|
|
3370
|
-
flex-shrink: 0;
|
|
3371
|
-
}
|
|
3372
|
-
|
|
3373
|
-
.playkit-toast-icon svg {
|
|
3374
|
-
width: 14px;
|
|
3375
|
-
height: 14px;
|
|
3376
|
-
color: #ffffff;
|
|
3377
|
-
}
|
|
3378
|
-
|
|
3379
|
-
.playkit-toast-message {
|
|
3380
|
-
flex: 1;
|
|
3381
|
-
font-size: 14px;
|
|
3382
|
-
font-weight: 500;
|
|
3383
|
-
color: #171717;
|
|
3384
|
-
line-height: 1.4;
|
|
3385
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3386
|
-
}
|
|
3387
|
-
|
|
3388
|
-
@media (max-width: 480px) {
|
|
3389
|
-
.playkit-daily-refresh-toast {
|
|
3390
|
-
top: 10px;
|
|
3391
|
-
right: 10px;
|
|
3392
|
-
left: 10px;
|
|
3393
|
-
min-width: auto;
|
|
3394
|
-
max-width: none;
|
|
3395
|
-
}
|
|
3396
|
-
}
|
|
3185
|
+
this.styleElement.textContent = `
|
|
3186
|
+
.playkit-recharge-overlay {
|
|
3187
|
+
position: fixed;
|
|
3188
|
+
top: 0;
|
|
3189
|
+
left: 0;
|
|
3190
|
+
right: 0;
|
|
3191
|
+
bottom: 0;
|
|
3192
|
+
background: rgba(0, 0, 0, 0.8);
|
|
3193
|
+
display: flex;
|
|
3194
|
+
justify-content: center;
|
|
3195
|
+
align-items: center;
|
|
3196
|
+
z-index: 999999;
|
|
3197
|
+
animation: playkit-recharge-fadeIn 0.2s ease-out;
|
|
3198
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3199
|
+
}
|
|
3200
|
+
|
|
3201
|
+
@keyframes playkit-recharge-fadeIn {
|
|
3202
|
+
from {
|
|
3203
|
+
opacity: 0;
|
|
3204
|
+
}
|
|
3205
|
+
to {
|
|
3206
|
+
opacity: 1;
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
3209
|
+
|
|
3210
|
+
.playkit-recharge-modal {
|
|
3211
|
+
background: #fff;
|
|
3212
|
+
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
3213
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
|
|
3214
|
+
padding: 24px;
|
|
3215
|
+
max-width: 320px;
|
|
3216
|
+
width: 90%;
|
|
3217
|
+
position: relative;
|
|
3218
|
+
text-align: center;
|
|
3219
|
+
}
|
|
3220
|
+
|
|
3221
|
+
.playkit-recharge-title {
|
|
3222
|
+
font-size: 14px;
|
|
3223
|
+
font-weight: 600;
|
|
3224
|
+
color: #171717;
|
|
3225
|
+
margin: 0 0 8px 0;
|
|
3226
|
+
text-align: center;
|
|
3227
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3228
|
+
}
|
|
3229
|
+
|
|
3230
|
+
.playkit-recharge-message {
|
|
3231
|
+
font-size: 14px;
|
|
3232
|
+
color: #666;
|
|
3233
|
+
margin: 0 0 20px 0;
|
|
3234
|
+
text-align: center;
|
|
3235
|
+
line-height: 1.5;
|
|
3236
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3237
|
+
}
|
|
3238
|
+
|
|
3239
|
+
.playkit-recharge-balance {
|
|
3240
|
+
background: #f5f5f5;
|
|
3241
|
+
border: 1px solid #e5e7eb;
|
|
3242
|
+
padding: 16px;
|
|
3243
|
+
margin: 0 0 20px 0;
|
|
3244
|
+
text-align: center;
|
|
3245
|
+
}
|
|
3246
|
+
|
|
3247
|
+
.playkit-recharge-balance-label {
|
|
3248
|
+
font-size: 12px;
|
|
3249
|
+
color: #666;
|
|
3250
|
+
margin: 0 0 8px 0;
|
|
3251
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3252
|
+
}
|
|
3253
|
+
|
|
3254
|
+
.playkit-recharge-balance-value {
|
|
3255
|
+
font-size: 24px;
|
|
3256
|
+
font-weight: bold;
|
|
3257
|
+
color: #171717;
|
|
3258
|
+
margin: 0;
|
|
3259
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3260
|
+
}
|
|
3261
|
+
|
|
3262
|
+
.playkit-recharge-balance-unit {
|
|
3263
|
+
font-size: 14px;
|
|
3264
|
+
color: #666;
|
|
3265
|
+
margin-left: 4px;
|
|
3266
|
+
}
|
|
3267
|
+
|
|
3268
|
+
.playkit-recharge-buttons {
|
|
3269
|
+
display: flex;
|
|
3270
|
+
flex-direction: column;
|
|
3271
|
+
gap: 8px;
|
|
3272
|
+
}
|
|
3273
|
+
|
|
3274
|
+
.playkit-recharge-button {
|
|
3275
|
+
width: 100%;
|
|
3276
|
+
padding: 10px 16px;
|
|
3277
|
+
border: none;
|
|
3278
|
+
font-size: 14px;
|
|
3279
|
+
font-weight: 500;
|
|
3280
|
+
cursor: pointer;
|
|
3281
|
+
transition: all 0.2s ease;
|
|
3282
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3283
|
+
}
|
|
3284
|
+
|
|
3285
|
+
.playkit-recharge-button-primary {
|
|
3286
|
+
background: #171717;
|
|
3287
|
+
color: white;
|
|
3288
|
+
}
|
|
3289
|
+
|
|
3290
|
+
.playkit-recharge-button-primary:hover {
|
|
3291
|
+
background: #404040;
|
|
3292
|
+
}
|
|
3293
|
+
|
|
3294
|
+
.playkit-recharge-button-primary:active {
|
|
3295
|
+
background: #0a0a0a;
|
|
3296
|
+
}
|
|
3297
|
+
|
|
3298
|
+
.playkit-recharge-button-secondary {
|
|
3299
|
+
background: transparent;
|
|
3300
|
+
color: #666;
|
|
3301
|
+
border: 1px solid #e5e7eb;
|
|
3302
|
+
}
|
|
3303
|
+
|
|
3304
|
+
.playkit-recharge-button-secondary:hover {
|
|
3305
|
+
background: #f5f5f5;
|
|
3306
|
+
border-color: #d4d4d4;
|
|
3307
|
+
}
|
|
3308
|
+
|
|
3309
|
+
.playkit-recharge-button-secondary:active {
|
|
3310
|
+
background: #e5e5e5;
|
|
3311
|
+
}
|
|
3312
|
+
|
|
3313
|
+
@media (max-width: 480px) {
|
|
3314
|
+
.playkit-recharge-modal {
|
|
3315
|
+
padding: 20px;
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
|
|
3319
|
+
/* Daily Refresh Toast Styles */
|
|
3320
|
+
.playkit-daily-refresh-toast {
|
|
3321
|
+
position: fixed;
|
|
3322
|
+
top: 20px;
|
|
3323
|
+
right: 20px;
|
|
3324
|
+
background: #fff;
|
|
3325
|
+
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
3326
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.1);
|
|
3327
|
+
padding: 16px 20px;
|
|
3328
|
+
min-width: 240px;
|
|
3329
|
+
max-width: 320px;
|
|
3330
|
+
z-index: 999998;
|
|
3331
|
+
animation: playkit-toast-slideIn 0.3s ease-out;
|
|
3332
|
+
display: flex;
|
|
3333
|
+
align-items: flex-start;
|
|
3334
|
+
gap: 12px;
|
|
3335
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3336
|
+
}
|
|
3337
|
+
|
|
3338
|
+
.playkit-daily-refresh-toast.hiding {
|
|
3339
|
+
animation: playkit-toast-fadeOut 0.3s ease-out forwards;
|
|
3340
|
+
}
|
|
3341
|
+
|
|
3342
|
+
@keyframes playkit-toast-slideIn {
|
|
3343
|
+
from {
|
|
3344
|
+
transform: translateX(100%);
|
|
3345
|
+
opacity: 0;
|
|
3346
|
+
}
|
|
3347
|
+
to {
|
|
3348
|
+
transform: translateX(0);
|
|
3349
|
+
opacity: 1;
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
|
|
3353
|
+
@keyframes playkit-toast-fadeOut {
|
|
3354
|
+
from {
|
|
3355
|
+
transform: translateX(0);
|
|
3356
|
+
opacity: 1;
|
|
3357
|
+
}
|
|
3358
|
+
to {
|
|
3359
|
+
transform: translateX(100%);
|
|
3360
|
+
opacity: 0;
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
|
|
3364
|
+
.playkit-toast-icon {
|
|
3365
|
+
width: 24px;
|
|
3366
|
+
height: 24px;
|
|
3367
|
+
background: #171717;
|
|
3368
|
+
border-radius: 50%;
|
|
3369
|
+
display: flex;
|
|
3370
|
+
align-items: center;
|
|
3371
|
+
justify-content: center;
|
|
3372
|
+
flex-shrink: 0;
|
|
3373
|
+
}
|
|
3374
|
+
|
|
3375
|
+
.playkit-toast-icon svg {
|
|
3376
|
+
width: 14px;
|
|
3377
|
+
height: 14px;
|
|
3378
|
+
color: #ffffff;
|
|
3379
|
+
}
|
|
3380
|
+
|
|
3381
|
+
.playkit-toast-message {
|
|
3382
|
+
flex: 1;
|
|
3383
|
+
font-size: 14px;
|
|
3384
|
+
font-weight: 500;
|
|
3385
|
+
color: #171717;
|
|
3386
|
+
line-height: 1.4;
|
|
3387
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3388
|
+
}
|
|
3389
|
+
|
|
3390
|
+
@media (max-width: 480px) {
|
|
3391
|
+
.playkit-daily-refresh-toast {
|
|
3392
|
+
top: 10px;
|
|
3393
|
+
right: 10px;
|
|
3394
|
+
left: 10px;
|
|
3395
|
+
min-width: auto;
|
|
3396
|
+
max-width: none;
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3397
3399
|
`;
|
|
3398
3400
|
document.head.appendChild(this.styleElement);
|
|
3399
3401
|
}
|
|
@@ -3530,7 +3532,8 @@ class RechargeManager extends EventEmitter {
|
|
|
3530
3532
|
/**
|
|
3531
3533
|
* Player client for managing player information and credits
|
|
3532
3534
|
*/
|
|
3533
|
-
|
|
3535
|
+
// @ts-ignore - replaced at build time
|
|
3536
|
+
const DEFAULT_BASE_URL$5 = "https://api.playkit.ai";
|
|
3534
3537
|
const PLAYER_INFO_ENDPOINT = '/api/external/player-info';
|
|
3535
3538
|
const SET_NICKNAME_ENDPOINT = '/api/external/set-game-player-nickname';
|
|
3536
3539
|
class PlayerClient extends EventEmitter {
|
|
@@ -3542,13 +3545,13 @@ class PlayerClient extends EventEmitter {
|
|
|
3542
3545
|
this.balanceCheckInterval = null;
|
|
3543
3546
|
this.logger = Logger.getLogger('PlayerClient');
|
|
3544
3547
|
this.authManager = authManager;
|
|
3545
|
-
this.baseURL = config.baseURL || DEFAULT_BASE_URL$
|
|
3548
|
+
this.baseURL = config.baseURL || DEFAULT_BASE_URL$5;
|
|
3546
3549
|
this.gameId = config.gameId;
|
|
3547
3550
|
this.rechargeConfig = {
|
|
3548
3551
|
autoShowBalanceModal: (_a = rechargeConfig.autoShowBalanceModal) !== null && _a !== void 0 ? _a : true,
|
|
3549
3552
|
balanceCheckInterval: (_b = rechargeConfig.balanceCheckInterval) !== null && _b !== void 0 ? _b : 30000,
|
|
3550
3553
|
checkBalanceAfterApiCall: (_c = rechargeConfig.checkBalanceAfterApiCall) !== null && _c !== void 0 ? _c : true,
|
|
3551
|
-
rechargePortalUrl: rechargeConfig.rechargePortalUrl || 'https://playkit.ai/recharge',
|
|
3554
|
+
rechargePortalUrl: rechargeConfig.rechargePortalUrl || 'https://players.playkit.ai/recharge',
|
|
3552
3555
|
showDailyRefreshToast: (_d = rechargeConfig.showDailyRefreshToast) !== null && _d !== void 0 ? _d : true,
|
|
3553
3556
|
};
|
|
3554
3557
|
}
|
|
@@ -3563,9 +3566,7 @@ class PlayerClient extends EventEmitter {
|
|
|
3563
3566
|
}
|
|
3564
3567
|
try {
|
|
3565
3568
|
// Build headers with X-Game-Id to support Global Developer Token
|
|
3566
|
-
const headers = {
|
|
3567
|
-
Authorization: `Bearer ${token}`,
|
|
3568
|
-
};
|
|
3569
|
+
const headers = Object.assign({ Authorization: `Bearer ${token}` }, getSDKHeaders());
|
|
3569
3570
|
if (this.gameId) {
|
|
3570
3571
|
headers['X-Game-Id'] = this.gameId;
|
|
3571
3572
|
}
|
|
@@ -3665,10 +3666,7 @@ class PlayerClient extends EventEmitter {
|
|
|
3665
3666
|
try {
|
|
3666
3667
|
const response = await fetch(`${this.baseURL}${SET_NICKNAME_ENDPOINT}`, {
|
|
3667
3668
|
method: 'POST',
|
|
3668
|
-
headers: {
|
|
3669
|
-
Authorization: `Bearer ${token}`,
|
|
3670
|
-
'Content-Type': 'application/json',
|
|
3671
|
-
},
|
|
3669
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
3672
3670
|
body: JSON.stringify({ nickname: trimmed }),
|
|
3673
3671
|
});
|
|
3674
3672
|
if (!response.ok) {
|
|
@@ -3818,15 +3816,89 @@ class PlayerClient extends EventEmitter {
|
|
|
3818
3816
|
}
|
|
3819
3817
|
}
|
|
3820
3818
|
|
|
3819
|
+
const VALID_PART_TYPES = new Set([
|
|
3820
|
+
'text',
|
|
3821
|
+
'image',
|
|
3822
|
+
'image_url',
|
|
3823
|
+
'file',
|
|
3824
|
+
'audio',
|
|
3825
|
+
'input_audio',
|
|
3826
|
+
]);
|
|
3827
|
+
function describePart(part) {
|
|
3828
|
+
if (part === null)
|
|
3829
|
+
return 'null';
|
|
3830
|
+
if (typeof part !== 'object')
|
|
3831
|
+
return typeof part;
|
|
3832
|
+
const keys = Object.keys(part).slice(0, 5).join(',');
|
|
3833
|
+
return `{${keys}}`;
|
|
3834
|
+
}
|
|
3835
|
+
/**
|
|
3836
|
+
* Validate that `messages` matches the SDK's `Message[]` runtime contract before
|
|
3837
|
+
* shipping to the chat API. Throws `PlayKitError('INVALID_MESSAGES')` when a
|
|
3838
|
+
* caller has wrapped a Message[] inside one user message's `content` (the
|
|
3839
|
+
* `[{role:'user', content: [{role,...}, ...]}]` anti-pattern that bypasses the
|
|
3840
|
+
* `MessageContentPart` type at runtime).
|
|
3841
|
+
*
|
|
3842
|
+
* Does NOT auto-flatten — silently guessing system/user roles would mask bugs.
|
|
3843
|
+
*/
|
|
3844
|
+
function assertValidMessages(messages) {
|
|
3845
|
+
if (!Array.isArray(messages)) {
|
|
3846
|
+
throw new PlayKitError('messages must be an array of Message', 'INVALID_MESSAGES');
|
|
3847
|
+
}
|
|
3848
|
+
for (let i = 0; i < messages.length; i++) {
|
|
3849
|
+
const msg = messages[i];
|
|
3850
|
+
if (!msg || typeof msg !== 'object') {
|
|
3851
|
+
throw new PlayKitError(`messages[${i}] must be an object with {role, content}`, 'INVALID_MESSAGES');
|
|
3852
|
+
}
|
|
3853
|
+
const content = msg.content;
|
|
3854
|
+
if (typeof content === 'string' || content == null)
|
|
3855
|
+
continue;
|
|
3856
|
+
if (!Array.isArray(content)) {
|
|
3857
|
+
throw new PlayKitError(`messages[${i}].content must be a string or an array of content parts (got ${typeof content})`, 'INVALID_MESSAGES');
|
|
3858
|
+
}
|
|
3859
|
+
for (let j = 0; j < content.length; j++) {
|
|
3860
|
+
const part = content[j];
|
|
3861
|
+
if (!part || typeof part !== 'object') {
|
|
3862
|
+
throw new PlayKitError(`messages[${i}].content[${j}] must be a content part object (got ${typeof part})`, 'INVALID_MESSAGES');
|
|
3863
|
+
}
|
|
3864
|
+
const hasType = typeof part.type === 'string' && VALID_PART_TYPES.has(part.type);
|
|
3865
|
+
if (!hasType) {
|
|
3866
|
+
if ('role' in part && 'content' in part) {
|
|
3867
|
+
throw new PlayKitError(`messages[${i}].content[${j}] is shaped like a Message (has role/content) ` +
|
|
3868
|
+
`but content parts must be {type:'text'|'image'|'image_url'|'file'|'audio'|'input_audio',...}. ` +
|
|
3869
|
+
`Did you mean to pass that array as messages directly? ` +
|
|
3870
|
+
`e.g. \`messages: theArray\` instead of \`messages: [{role:'user', content: theArray}]\`. ` +
|
|
3871
|
+
`Got part ${describePart(part)}`, 'INVALID_MESSAGES');
|
|
3872
|
+
}
|
|
3873
|
+
throw new PlayKitError(`messages[${i}].content[${j}] is missing a recognized 'type' field ` +
|
|
3874
|
+
`(expected one of text|image|image_url|file|audio|input_audio). Got part ${describePart(part)}`, 'INVALID_MESSAGES');
|
|
3875
|
+
}
|
|
3876
|
+
}
|
|
3877
|
+
}
|
|
3878
|
+
}
|
|
3879
|
+
|
|
3821
3880
|
/**
|
|
3822
3881
|
* Chat provider for HTTP communication with chat API
|
|
3823
3882
|
*/
|
|
3824
|
-
|
|
3883
|
+
/**
|
|
3884
|
+
* Helper to extract string from MessageContent
|
|
3885
|
+
*/
|
|
3886
|
+
function contentToString$1(content) {
|
|
3887
|
+
if (!content)
|
|
3888
|
+
return '';
|
|
3889
|
+
if (typeof content === 'string')
|
|
3890
|
+
return content;
|
|
3891
|
+
// For array of content parts, extract text parts
|
|
3892
|
+
const textParts = content.filter(part => part.type === 'text');
|
|
3893
|
+
return textParts.map(part => part.text).join('');
|
|
3894
|
+
}
|
|
3895
|
+
// @ts-ignore - replaced at build time
|
|
3896
|
+
const DEFAULT_BASE_URL$4 = "https://api.playkit.ai";
|
|
3825
3897
|
class ChatProvider {
|
|
3826
3898
|
constructor(authManager, config) {
|
|
3827
3899
|
this.authManager = authManager;
|
|
3828
3900
|
this.config = config;
|
|
3829
|
-
this.baseURL = config.baseURL || DEFAULT_BASE_URL$
|
|
3901
|
+
this.baseURL = config.baseURL || DEFAULT_BASE_URL$4;
|
|
3830
3902
|
}
|
|
3831
3903
|
/**
|
|
3832
3904
|
* Set player client for balance checking
|
|
@@ -3839,6 +3911,7 @@ class ChatProvider {
|
|
|
3839
3911
|
*/
|
|
3840
3912
|
async chatCompletion(chatConfig) {
|
|
3841
3913
|
var _a;
|
|
3914
|
+
assertValidMessages(chatConfig.messages);
|
|
3842
3915
|
// Ensure token is valid, auto-refresh if needed (browser mode only)
|
|
3843
3916
|
await this.authManager.ensureValidToken();
|
|
3844
3917
|
const token = this.authManager.getToken();
|
|
@@ -3860,10 +3933,7 @@ class ChatProvider {
|
|
|
3860
3933
|
try {
|
|
3861
3934
|
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
3862
3935
|
method: 'POST',
|
|
3863
|
-
headers: {
|
|
3864
|
-
Authorization: `Bearer ${token}`,
|
|
3865
|
-
'Content-Type': 'application/json',
|
|
3866
|
-
},
|
|
3936
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
3867
3937
|
body: JSON.stringify(requestBody),
|
|
3868
3938
|
});
|
|
3869
3939
|
if (!response.ok) {
|
|
@@ -3898,6 +3968,7 @@ class ChatProvider {
|
|
|
3898
3968
|
*/
|
|
3899
3969
|
async chatCompletionStream(chatConfig) {
|
|
3900
3970
|
var _a;
|
|
3971
|
+
assertValidMessages(chatConfig.messages);
|
|
3901
3972
|
// Ensure token is valid, auto-refresh if needed (browser mode only)
|
|
3902
3973
|
await this.authManager.ensureValidToken();
|
|
3903
3974
|
const token = this.authManager.getToken();
|
|
@@ -3919,10 +3990,7 @@ class ChatProvider {
|
|
|
3919
3990
|
try {
|
|
3920
3991
|
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
3921
3992
|
method: 'POST',
|
|
3922
|
-
headers: {
|
|
3923
|
-
Authorization: `Bearer ${token}`,
|
|
3924
|
-
'Content-Type': 'application/json',
|
|
3925
|
-
},
|
|
3993
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
3926
3994
|
body: JSON.stringify(requestBody),
|
|
3927
3995
|
});
|
|
3928
3996
|
if (!response.ok) {
|
|
@@ -3959,6 +4027,7 @@ class ChatProvider {
|
|
|
3959
4027
|
*/
|
|
3960
4028
|
async chatCompletionWithTools(chatConfig) {
|
|
3961
4029
|
var _a, _b;
|
|
4030
|
+
assertValidMessages(chatConfig.messages);
|
|
3962
4031
|
const token = this.authManager.getToken();
|
|
3963
4032
|
if (!token) {
|
|
3964
4033
|
throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
|
|
@@ -3985,10 +4054,7 @@ class ChatProvider {
|
|
|
3985
4054
|
try {
|
|
3986
4055
|
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
3987
4056
|
method: 'POST',
|
|
3988
|
-
headers: {
|
|
3989
|
-
Authorization: `Bearer ${token}`,
|
|
3990
|
-
'Content-Type': 'application/json',
|
|
3991
|
-
},
|
|
4057
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
3992
4058
|
body: JSON.stringify(requestBody),
|
|
3993
4059
|
});
|
|
3994
4060
|
if (!response.ok) {
|
|
@@ -4019,6 +4085,7 @@ class ChatProvider {
|
|
|
4019
4085
|
*/
|
|
4020
4086
|
async chatCompletionWithToolsStream(chatConfig) {
|
|
4021
4087
|
var _a, _b;
|
|
4088
|
+
assertValidMessages(chatConfig.messages);
|
|
4022
4089
|
const token = this.authManager.getToken();
|
|
4023
4090
|
if (!token) {
|
|
4024
4091
|
throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
|
|
@@ -4045,10 +4112,7 @@ class ChatProvider {
|
|
|
4045
4112
|
try {
|
|
4046
4113
|
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
4047
4114
|
method: 'POST',
|
|
4048
|
-
headers: {
|
|
4049
|
-
Authorization: `Bearer ${token}`,
|
|
4050
|
-
'Content-Type': 'application/json',
|
|
4051
|
-
},
|
|
4115
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
4052
4116
|
body: JSON.stringify(requestBody),
|
|
4053
4117
|
});
|
|
4054
4118
|
if (!response.ok) {
|
|
@@ -4118,10 +4182,7 @@ class ChatProvider {
|
|
|
4118
4182
|
try {
|
|
4119
4183
|
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
4120
4184
|
method: 'POST',
|
|
4121
|
-
headers: {
|
|
4122
|
-
Authorization: `Bearer ${token}`,
|
|
4123
|
-
'Content-Type': 'application/json',
|
|
4124
|
-
},
|
|
4185
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
4125
4186
|
body: JSON.stringify(requestBody),
|
|
4126
4187
|
});
|
|
4127
4188
|
if (!response.ok) {
|
|
@@ -4139,11 +4200,12 @@ class ChatProvider {
|
|
|
4139
4200
|
this.playerClient.checkBalanceAfterApiCall().catch(() => { });
|
|
4140
4201
|
}
|
|
4141
4202
|
// Parse the response content as JSON
|
|
4142
|
-
const
|
|
4143
|
-
if (!
|
|
4203
|
+
const rawContent = (_a = result.choices[0]) === null || _a === void 0 ? void 0 : _a.message.content;
|
|
4204
|
+
if (!rawContent) {
|
|
4144
4205
|
throw new PlayKitError('No content in response', 'NO_CONTENT');
|
|
4145
4206
|
}
|
|
4146
4207
|
try {
|
|
4208
|
+
const content = contentToString$1(rawContent);
|
|
4147
4209
|
return JSON.parse(content);
|
|
4148
4210
|
}
|
|
4149
4211
|
catch (parseError) {
|
|
@@ -4162,12 +4224,13 @@ class ChatProvider {
|
|
|
4162
4224
|
/**
|
|
4163
4225
|
* Image generation provider for HTTP communication with image API
|
|
4164
4226
|
*/
|
|
4165
|
-
|
|
4227
|
+
// @ts-ignore - replaced at build time
|
|
4228
|
+
const DEFAULT_BASE_URL$3 = "https://api.playkit.ai";
|
|
4166
4229
|
class ImageProvider {
|
|
4167
4230
|
constructor(authManager, config) {
|
|
4168
4231
|
this.authManager = authManager;
|
|
4169
4232
|
this.config = config;
|
|
4170
|
-
this.baseURL = config.baseURL || DEFAULT_BASE_URL$
|
|
4233
|
+
this.baseURL = config.baseURL || DEFAULT_BASE_URL$3;
|
|
4171
4234
|
}
|
|
4172
4235
|
/**
|
|
4173
4236
|
* Set player client for balance checking
|
|
@@ -4215,10 +4278,7 @@ class ImageProvider {
|
|
|
4215
4278
|
try {
|
|
4216
4279
|
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
4217
4280
|
method: 'POST',
|
|
4218
|
-
headers: {
|
|
4219
|
-
Authorization: `Bearer ${token}`,
|
|
4220
|
-
'Content-Type': 'application/json',
|
|
4221
|
-
},
|
|
4281
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
4222
4282
|
body: JSON.stringify(requestBody),
|
|
4223
4283
|
});
|
|
4224
4284
|
if (!response.ok) {
|
|
@@ -4253,12 +4313,13 @@ class ImageProvider {
|
|
|
4253
4313
|
/**
|
|
4254
4314
|
* Transcription provider for HTTP communication with audio transcription API
|
|
4255
4315
|
*/
|
|
4256
|
-
|
|
4316
|
+
// @ts-ignore - replaced at build time
|
|
4317
|
+
const DEFAULT_BASE_URL$2 = "https://api.playkit.ai";
|
|
4257
4318
|
class TranscriptionProvider {
|
|
4258
4319
|
constructor(authManager, config) {
|
|
4259
4320
|
this.authManager = authManager;
|
|
4260
4321
|
this.config = config;
|
|
4261
|
-
this.baseURL = config.baseURL || DEFAULT_BASE_URL$
|
|
4322
|
+
this.baseURL = config.baseURL || DEFAULT_BASE_URL$2;
|
|
4262
4323
|
}
|
|
4263
4324
|
/**
|
|
4264
4325
|
* Set player client for balance checking
|
|
@@ -4318,10 +4379,7 @@ class TranscriptionProvider {
|
|
|
4318
4379
|
try {
|
|
4319
4380
|
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
4320
4381
|
method: 'POST',
|
|
4321
|
-
headers: {
|
|
4322
|
-
Authorization: `Bearer ${token}`,
|
|
4323
|
-
'Content-Type': 'application/json',
|
|
4324
|
-
},
|
|
4382
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
4325
4383
|
body: JSON.stringify(requestBody),
|
|
4326
4384
|
});
|
|
4327
4385
|
if (!response.ok) {
|
|
@@ -4355,48 +4413,158 @@ class TranscriptionProvider {
|
|
|
4355
4413
|
}
|
|
4356
4414
|
}
|
|
4357
4415
|
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4416
|
+
/**
|
|
4417
|
+
* TTS provider for HTTP communication with the text-to-speech API
|
|
4418
|
+
*/
|
|
4419
|
+
// @ts-ignore - replaced at build time
|
|
4420
|
+
const DEFAULT_BASE_URL$1 = "https://api.playkit.ai";
|
|
4421
|
+
class TTSProvider {
|
|
4422
|
+
constructor(authManager, config) {
|
|
4423
|
+
this.authManager = authManager;
|
|
4424
|
+
this.config = config;
|
|
4425
|
+
this.baseURL = config.baseURL || DEFAULT_BASE_URL$1;
|
|
4426
|
+
}
|
|
4427
|
+
/**
|
|
4428
|
+
* Set player client for balance checking
|
|
4429
|
+
*/
|
|
4430
|
+
setPlayerClient(playerClient) {
|
|
4431
|
+
this.playerClient = playerClient;
|
|
4432
|
+
}
|
|
4433
|
+
/**
|
|
4434
|
+
* Synthesize text into speech audio
|
|
4435
|
+
*/
|
|
4436
|
+
async synthesize(ttsConfig) {
|
|
4437
|
+
// Ensure token is valid, auto-refresh if needed (browser mode only)
|
|
4438
|
+
await this.authManager.ensureValidToken();
|
|
4439
|
+
const token = this.authManager.getToken();
|
|
4440
|
+
if (!token) {
|
|
4441
|
+
throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
|
|
4442
|
+
}
|
|
4443
|
+
const model = ttsConfig.model || this.config.defaultTTSModel || 'default-tts-model';
|
|
4444
|
+
const endpoint = `/ai/${this.config.gameId}/v2/audio/speech`;
|
|
4445
|
+
const requestBody = {
|
|
4446
|
+
model,
|
|
4447
|
+
text: ttsConfig.text,
|
|
4448
|
+
};
|
|
4449
|
+
// Add optional parameters (only when defined)
|
|
4450
|
+
if (ttsConfig.voice !== undefined) {
|
|
4451
|
+
requestBody.voice = ttsConfig.voice;
|
|
4452
|
+
}
|
|
4453
|
+
if (ttsConfig.speed !== undefined) {
|
|
4454
|
+
requestBody.speed = ttsConfig.speed;
|
|
4455
|
+
}
|
|
4456
|
+
if (ttsConfig.vol !== undefined) {
|
|
4457
|
+
requestBody.vol = ttsConfig.vol;
|
|
4458
|
+
}
|
|
4459
|
+
if (ttsConfig.pitch !== undefined) {
|
|
4460
|
+
requestBody.pitch = ttsConfig.pitch;
|
|
4461
|
+
}
|
|
4462
|
+
if (ttsConfig.emotion !== undefined) {
|
|
4463
|
+
requestBody.emotion = ttsConfig.emotion;
|
|
4464
|
+
}
|
|
4465
|
+
if (ttsConfig.languageBoost !== undefined) {
|
|
4466
|
+
requestBody.language_boost = ttsConfig.languageBoost;
|
|
4467
|
+
}
|
|
4468
|
+
if (ttsConfig.format !== undefined) {
|
|
4469
|
+
requestBody.response_format = ttsConfig.format;
|
|
4470
|
+
}
|
|
4471
|
+
if (ttsConfig.voiceSetting !== undefined) {
|
|
4472
|
+
requestBody.voice_setting = ttsConfig.voiceSetting;
|
|
4473
|
+
}
|
|
4474
|
+
if (ttsConfig.audioSetting !== undefined) {
|
|
4475
|
+
requestBody.audio_setting = ttsConfig.audioSetting;
|
|
4476
|
+
}
|
|
4477
|
+
try {
|
|
4478
|
+
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
4479
|
+
method: 'POST',
|
|
4480
|
+
headers: Object.assign({ Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, getSDKHeaders()),
|
|
4481
|
+
body: JSON.stringify(requestBody),
|
|
4482
|
+
});
|
|
4483
|
+
if (!response.ok) {
|
|
4484
|
+
const error = await response.json().catch(() => ({ message: 'Speech synthesis failed' }));
|
|
4485
|
+
const playKitError = new PlayKitError(error.message || 'Speech synthesis failed', error.code, response.status);
|
|
4486
|
+
// Check for insufficient credits error
|
|
4487
|
+
if (error.code === 'INSUFFICIENT_CREDITS' ||
|
|
4488
|
+
error.code === 'PLAYER_INSUFFICIENT_CREDIT' ||
|
|
4489
|
+
response.status === 402) {
|
|
4490
|
+
if (this.playerClient) {
|
|
4491
|
+
await this.playerClient.handleInsufficientCredits(playKitError);
|
|
4492
|
+
}
|
|
4493
|
+
}
|
|
4494
|
+
throw playKitError;
|
|
4495
|
+
}
|
|
4496
|
+
// SUCCESS: response is raw audio bytes, NOT JSON.
|
|
4497
|
+
const audio = await response.arrayBuffer();
|
|
4498
|
+
const contentType = response.headers.get('Content-Type');
|
|
4499
|
+
const usageHeader = response.headers.get('X-Usage-Characters');
|
|
4500
|
+
const audioLengthHeader = response.headers.get('X-Audio-Length-Ms');
|
|
4501
|
+
const result = {
|
|
4502
|
+
audio,
|
|
4503
|
+
format: contentType || ttsConfig.format || 'mp3',
|
|
4504
|
+
usageCharacters: Number(usageHeader) || 0,
|
|
4505
|
+
};
|
|
4506
|
+
if (audioLengthHeader !== null) {
|
|
4507
|
+
result.audioLengthMs = Number(audioLengthHeader) || 0;
|
|
4508
|
+
}
|
|
4509
|
+
// Check balance after successful API call
|
|
4510
|
+
if (this.playerClient) {
|
|
4511
|
+
this.playerClient.checkBalanceAfterApiCall().catch(() => {
|
|
4512
|
+
// Silently fail
|
|
4513
|
+
});
|
|
4514
|
+
}
|
|
4515
|
+
return result;
|
|
4516
|
+
}
|
|
4517
|
+
catch (error) {
|
|
4518
|
+
if (error instanceof PlayKitError) {
|
|
4519
|
+
throw error;
|
|
4520
|
+
}
|
|
4521
|
+
throw new PlayKitError(error instanceof Error ? error.message : 'Unknown error', 'TTS_ERROR');
|
|
4522
|
+
}
|
|
4523
|
+
}
|
|
4524
|
+
}
|
|
4525
|
+
|
|
4526
|
+
/******************************************************************************
|
|
4527
|
+
Copyright (c) Microsoft Corporation.
|
|
4528
|
+
|
|
4529
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
4530
|
+
purpose with or without fee is hereby granted.
|
|
4531
|
+
|
|
4532
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
4533
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
4534
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
4535
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
4536
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
4537
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
4538
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
4539
|
+
***************************************************************************** */
|
|
4540
|
+
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
4541
|
+
|
|
4542
|
+
|
|
4543
|
+
function __values(o) {
|
|
4544
|
+
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
4545
|
+
if (m) return m.call(o);
|
|
4546
|
+
if (o && typeof o.length === "number") return {
|
|
4547
|
+
next: function () {
|
|
4548
|
+
if (o && i >= o.length) o = void 0;
|
|
4549
|
+
return { value: o && o[i++], done: !o };
|
|
4550
|
+
}
|
|
4551
|
+
};
|
|
4552
|
+
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
4553
|
+
}
|
|
4554
|
+
|
|
4555
|
+
function __await(v) {
|
|
4556
|
+
return this instanceof __await ? (this.v = v, this) : new __await(v);
|
|
4557
|
+
}
|
|
4558
|
+
|
|
4559
|
+
function __asyncGenerator(thisArg, _arguments, generator) {
|
|
4560
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
4561
|
+
var g = generator.apply(thisArg, _arguments || []), i, q = [];
|
|
4562
|
+
return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
|
|
4563
|
+
function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
|
|
4564
|
+
function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
|
|
4565
|
+
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
|
|
4566
|
+
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
|
|
4567
|
+
function fulfill(value) { resume("next", value); }
|
|
4400
4568
|
function reject(value) { resume("throw", value); }
|
|
4401
4569
|
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
|
|
4402
4570
|
}
|
|
@@ -4467,9 +4635,18 @@ class StreamParser {
|
|
|
4467
4635
|
if (text) {
|
|
4468
4636
|
yield yield __await(text);
|
|
4469
4637
|
}
|
|
4470
|
-
|
|
4638
|
+
// Stream termination events
|
|
4639
|
+
if (parsed.type === 'done' || parsed.type === 'finish' || parsed.finish_reason) {
|
|
4640
|
+
return yield __await(void 0);
|
|
4641
|
+
}
|
|
4642
|
+
if (parsed.type === 'abort') {
|
|
4643
|
+
// Server-side timeout or cancellation — treat as end of stream
|
|
4471
4644
|
return yield __await(void 0);
|
|
4472
4645
|
}
|
|
4646
|
+
if (parsed.type === 'error') {
|
|
4647
|
+
// Server-side error event — throw to trigger onError callback
|
|
4648
|
+
throw new Error(parsed.errorText || parsed.error || 'Stream error');
|
|
4649
|
+
}
|
|
4473
4650
|
}
|
|
4474
4651
|
catch (error) {
|
|
4475
4652
|
// If JSON parse fails, treat as plain text
|
|
@@ -4568,6 +4745,18 @@ class StreamParser {
|
|
|
4568
4745
|
/**
|
|
4569
4746
|
* Chat client for AI text generation
|
|
4570
4747
|
*/
|
|
4748
|
+
/**
|
|
4749
|
+
* Helper to extract string from MessageContent
|
|
4750
|
+
*/
|
|
4751
|
+
function contentToString(content) {
|
|
4752
|
+
if (!content)
|
|
4753
|
+
return '';
|
|
4754
|
+
if (typeof content === 'string')
|
|
4755
|
+
return content;
|
|
4756
|
+
// For array of content parts, extract text parts
|
|
4757
|
+
const textParts = content.filter(part => part.type === 'text');
|
|
4758
|
+
return textParts.map(part => part.text).join('');
|
|
4759
|
+
}
|
|
4571
4760
|
class ChatClient {
|
|
4572
4761
|
constructor(provider, model) {
|
|
4573
4762
|
this.schemaLibrary = null;
|
|
@@ -4617,7 +4806,7 @@ class ChatClient {
|
|
|
4617
4806
|
throw new Error('No choices in response');
|
|
4618
4807
|
}
|
|
4619
4808
|
return {
|
|
4620
|
-
content: choice.message.content,
|
|
4809
|
+
content: contentToString(choice.message.content),
|
|
4621
4810
|
model: response.model,
|
|
4622
4811
|
finishReason: choice.finish_reason,
|
|
4623
4812
|
usage: response.usage
|
|
@@ -4688,9 +4877,10 @@ class ChatClient {
|
|
|
4688
4877
|
}
|
|
4689
4878
|
// Extract user message content from the last user message
|
|
4690
4879
|
const lastUserMessage = [...messages].reverse().find(m => m.role === 'user');
|
|
4691
|
-
const prompt = (lastUserMessage === null || lastUserMessage === void 0 ? void 0 : lastUserMessage.content)
|
|
4880
|
+
const prompt = contentToString(lastUserMessage === null || lastUserMessage === void 0 ? void 0 : lastUserMessage.content);
|
|
4692
4881
|
// Build system message from messages array
|
|
4693
|
-
const
|
|
4882
|
+
const systemMessageContent = (_a = messages.find(m => m.role === 'system')) === null || _a === void 0 ? void 0 : _a.content;
|
|
4883
|
+
const systemMessage = contentToString(systemMessageContent) || undefined;
|
|
4694
4884
|
return this.generateStructuredWithSchema(schemaEntry.schema, prompt, Object.assign({ schemaName, schemaDescription: schemaEntry.description, systemMessage }, options));
|
|
4695
4885
|
}
|
|
4696
4886
|
/**
|
|
@@ -4781,7 +4971,7 @@ class ChatClient {
|
|
|
4781
4971
|
throw new Error('No choices in response');
|
|
4782
4972
|
}
|
|
4783
4973
|
return {
|
|
4784
|
-
content: choice.message.content
|
|
4974
|
+
content: contentToString(choice.message.content),
|
|
4785
4975
|
model: response.model,
|
|
4786
4976
|
finishReason: choice.finish_reason,
|
|
4787
4977
|
usage: response.usage
|
|
@@ -4845,7 +5035,7 @@ class GeneratedImageImpl {
|
|
|
4845
5035
|
return new Promise((resolve, reject) => {
|
|
4846
5036
|
const img = new Image();
|
|
4847
5037
|
img.onload = () => resolve(img);
|
|
4848
|
-
img.onerror = (
|
|
5038
|
+
img.onerror = (_e) => reject(new Error('Failed to load image'));
|
|
4849
5039
|
img.src = this.toDataURL();
|
|
4850
5040
|
});
|
|
4851
5041
|
}
|
|
@@ -4859,13 +5049,14 @@ class ImageClient {
|
|
|
4859
5049
|
* Generate a single image
|
|
4860
5050
|
*/
|
|
4861
5051
|
async generateImage(config) {
|
|
5052
|
+
var _a;
|
|
4862
5053
|
const imageConfig = Object.assign(Object.assign({}, config), { model: config.model || this.model, n: 1 });
|
|
4863
5054
|
const response = await this.provider.generateImages(imageConfig);
|
|
4864
5055
|
const imageData = response.data[0];
|
|
4865
5056
|
if (!imageData || !imageData.b64_json) {
|
|
4866
5057
|
throw new Error('No image data in response');
|
|
4867
5058
|
}
|
|
4868
|
-
return new GeneratedImageImpl(imageData.b64_json, config.prompt, imageData.revised_prompt, config.size, imageData.b64_json_original, imageData.transparent_success);
|
|
5059
|
+
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);
|
|
4869
5060
|
}
|
|
4870
5061
|
/**
|
|
4871
5062
|
* Generate multiple images
|
|
@@ -4874,10 +5065,11 @@ class ImageClient {
|
|
|
4874
5065
|
const imageConfig = Object.assign(Object.assign({}, config), { model: config.model || this.model, n: config.n || 1 });
|
|
4875
5066
|
const response = await this.provider.generateImages(imageConfig);
|
|
4876
5067
|
return response.data.map((imageData) => {
|
|
5068
|
+
var _a;
|
|
4877
5069
|
if (!imageData.b64_json) {
|
|
4878
5070
|
throw new Error('No image data in response');
|
|
4879
5071
|
}
|
|
4880
|
-
return new GeneratedImageImpl(imageData.b64_json, config.prompt, imageData.revised_prompt, config.size, imageData.b64_json_original, imageData.transparent_success);
|
|
5072
|
+
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);
|
|
4881
5073
|
});
|
|
4882
5074
|
}
|
|
4883
5075
|
/**
|
|
@@ -4996,1019 +5188,1191 @@ class TranscriptionClient {
|
|
|
4996
5188
|
}
|
|
4997
5189
|
|
|
4998
5190
|
/**
|
|
4999
|
-
*
|
|
5000
|
-
* Automatically handles conversation history
|
|
5001
|
-
*
|
|
5002
|
-
* Key Features:
|
|
5003
|
-
* - Call talk() for all interactions - actions are handled automatically
|
|
5004
|
-
* - Memory system for persistent NPC context
|
|
5005
|
-
* - Reply prediction for suggesting player responses
|
|
5006
|
-
* - Automatic conversation history management
|
|
5191
|
+
* High-level client for text-to-speech synthesis
|
|
5007
5192
|
*/
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5193
|
+
/**
|
|
5194
|
+
* Resolve an audio format/content-type string into a valid MIME type.
|
|
5195
|
+
* The provider's `result.format` may be a full MIME (e.g. 'audio/mpeg' from
|
|
5196
|
+
* the Content-Type header) or a bare token (e.g. 'mp3' from the fallback).
|
|
5197
|
+
* Full MIME strings pass through; bare tokens are mapped.
|
|
5198
|
+
*/
|
|
5199
|
+
function contentTypeFor(format) {
|
|
5200
|
+
if (format.includes('/')) {
|
|
5201
|
+
return format;
|
|
5202
|
+
}
|
|
5203
|
+
switch (format.toLowerCase()) {
|
|
5204
|
+
case 'mp3':
|
|
5205
|
+
return 'audio/mpeg';
|
|
5206
|
+
case 'wav':
|
|
5207
|
+
return 'audio/wav';
|
|
5208
|
+
case 'ogg':
|
|
5209
|
+
return 'audio/ogg';
|
|
5210
|
+
case 'flac':
|
|
5211
|
+
return 'audio/flac';
|
|
5212
|
+
case 'aac':
|
|
5213
|
+
return 'audio/aac';
|
|
5214
|
+
case 'pcm':
|
|
5215
|
+
return 'audio/pcm';
|
|
5216
|
+
default:
|
|
5217
|
+
return 'audio/mpeg';
|
|
5024
5218
|
}
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
return this._isTalking;
|
|
5219
|
+
}
|
|
5220
|
+
class TTSClient {
|
|
5221
|
+
constructor(provider, model) {
|
|
5222
|
+
this.provider = provider;
|
|
5223
|
+
this.model = model || 'default-tts-model';
|
|
5031
5224
|
}
|
|
5032
|
-
// ===== Character Design & Memory System =====
|
|
5033
5225
|
/**
|
|
5034
|
-
*
|
|
5035
|
-
* The system prompt is composed of CharacterDesign + all Memories.
|
|
5226
|
+
* Get the current model name
|
|
5036
5227
|
*/
|
|
5037
|
-
|
|
5038
|
-
this.
|
|
5228
|
+
get modelName() {
|
|
5229
|
+
return this.model;
|
|
5039
5230
|
}
|
|
5040
5231
|
/**
|
|
5041
|
-
*
|
|
5232
|
+
* Synthesize text into speech audio
|
|
5233
|
+
* @param config - Full TTS configuration
|
|
5234
|
+
* @returns TTS result containing raw audio bytes and usage metadata
|
|
5042
5235
|
*/
|
|
5043
|
-
|
|
5044
|
-
return this.
|
|
5236
|
+
async synthesize(config) {
|
|
5237
|
+
return this.provider.synthesize(Object.assign(Object.assign({}, config), { model: config.model || this.model }));
|
|
5045
5238
|
}
|
|
5046
5239
|
/**
|
|
5047
|
-
*
|
|
5048
|
-
*
|
|
5240
|
+
* Synthesize text into speech and return it as a Blob (browser-friendly)
|
|
5241
|
+
* @param config - Full TTS configuration
|
|
5242
|
+
* @returns Audio Blob with the appropriate MIME type
|
|
5049
5243
|
*/
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5244
|
+
async synthesizeToBlob(config) {
|
|
5245
|
+
const result = await this.synthesize(config);
|
|
5246
|
+
return new Blob([result.audio], { type: contentTypeFor(result.format) });
|
|
5053
5247
|
}
|
|
5054
5248
|
/**
|
|
5055
|
-
*
|
|
5056
|
-
*
|
|
5249
|
+
* Synthesize text into speech and return an object URL (browser only)
|
|
5250
|
+
* @param config - Full TTS configuration
|
|
5251
|
+
* @returns An object URL that can be assigned to an <audio> element
|
|
5252
|
+
* @throws PlayKitError if URL.createObjectURL is unavailable (e.g. Node.js)
|
|
5057
5253
|
*/
|
|
5058
|
-
|
|
5059
|
-
|
|
5254
|
+
async synthesizeToObjectURL(config) {
|
|
5255
|
+
if (typeof URL === 'undefined' || typeof URL.createObjectURL !== 'function') {
|
|
5256
|
+
throw new PlayKitError('URL.createObjectURL is not available in this environment. ' +
|
|
5257
|
+
'Use synthesize() to access the raw audio bytes instead.', 'TTS_OBJECT_URL_UNAVAILABLE');
|
|
5258
|
+
}
|
|
5259
|
+
const blob = await this.synthesizeToBlob(config);
|
|
5260
|
+
return URL.createObjectURL(blob);
|
|
5261
|
+
}
|
|
5262
|
+
}
|
|
5263
|
+
|
|
5264
|
+
/**
|
|
5265
|
+
* Global AI Context Manager for managing NPC conversations and player context.
|
|
5266
|
+
*
|
|
5267
|
+
* Features:
|
|
5268
|
+
* - Player description management
|
|
5269
|
+
* - NPC conversation tracking
|
|
5270
|
+
* - Automatic conversation compaction (AutoCompact)
|
|
5271
|
+
*/
|
|
5272
|
+
/**
|
|
5273
|
+
* Global AI Context Manager
|
|
5274
|
+
* Manages NPC conversations and player context across the application
|
|
5275
|
+
*/
|
|
5276
|
+
class AIContextManager extends EventEmitter {
|
|
5277
|
+
constructor(config) {
|
|
5278
|
+
var _a, _b, _c, _d, _e;
|
|
5279
|
+
super();
|
|
5280
|
+
this.playerDescription = null;
|
|
5281
|
+
this.playerPrompt = null;
|
|
5282
|
+
this.playerMemories = new Map();
|
|
5283
|
+
this.npcStates = new Map();
|
|
5284
|
+
this.autoCompactTimer = null;
|
|
5285
|
+
this.chatClientFactory = null;
|
|
5286
|
+
this.logger = Logger.getLogger('AIContextManager');
|
|
5287
|
+
this.config = {
|
|
5288
|
+
enableAutoCompact: (_a = config === null || config === void 0 ? void 0 : config.enableAutoCompact) !== null && _a !== void 0 ? _a : false,
|
|
5289
|
+
autoCompactMinMessages: (_b = config === null || config === void 0 ? void 0 : config.autoCompactMinMessages) !== null && _b !== void 0 ? _b : 20,
|
|
5290
|
+
autoCompactTimeoutSeconds: (_c = config === null || config === void 0 ? void 0 : config.autoCompactTimeoutSeconds) !== null && _c !== void 0 ? _c : 300,
|
|
5291
|
+
autoCompactCheckInterval: (_d = config === null || config === void 0 ? void 0 : config.autoCompactCheckInterval) !== null && _d !== void 0 ? _d : 60000,
|
|
5292
|
+
fastModel: (_e = config === null || config === void 0 ? void 0 : config.fastModel) !== null && _e !== void 0 ? _e : '',
|
|
5293
|
+
};
|
|
5294
|
+
// Start auto-compact check if enabled
|
|
5295
|
+
if (this.config.enableAutoCompact) {
|
|
5296
|
+
this.startAutoCompactCheck();
|
|
5297
|
+
}
|
|
5060
5298
|
}
|
|
5299
|
+
// ===== Singleton Pattern =====
|
|
5061
5300
|
/**
|
|
5062
|
-
*
|
|
5063
|
-
*
|
|
5064
|
-
* Set memoryContent to null or empty to remove the memory.
|
|
5065
|
-
* @param memoryName The name/key of the memory
|
|
5066
|
-
* @param memoryContent The content of the memory. Null or empty to remove.
|
|
5301
|
+
* Get the singleton instance of AIContextManager
|
|
5302
|
+
* Creates a new instance if one doesn't exist
|
|
5067
5303
|
*/
|
|
5068
|
-
|
|
5069
|
-
if (!
|
|
5070
|
-
|
|
5071
|
-
return;
|
|
5072
|
-
}
|
|
5073
|
-
if (!memoryContent) {
|
|
5074
|
-
// Remove memory if content is null or empty
|
|
5075
|
-
if (this.memories.has(memoryName)) {
|
|
5076
|
-
this.memories.delete(memoryName);
|
|
5077
|
-
this.emit('memory_removed', memoryName);
|
|
5078
|
-
}
|
|
5079
|
-
}
|
|
5080
|
-
else {
|
|
5081
|
-
// Add or update memory
|
|
5082
|
-
this.memories.set(memoryName, memoryContent);
|
|
5083
|
-
this.emit('memory_set', memoryName, memoryContent);
|
|
5304
|
+
static getInstance(config) {
|
|
5305
|
+
if (!AIContextManager._instance) {
|
|
5306
|
+
AIContextManager._instance = new AIContextManager(config);
|
|
5084
5307
|
}
|
|
5308
|
+
return AIContextManager._instance;
|
|
5085
5309
|
}
|
|
5086
5310
|
/**
|
|
5087
|
-
*
|
|
5088
|
-
* @param memoryName The name of the memory to retrieve
|
|
5089
|
-
* @returns The memory content, or undefined if not found
|
|
5311
|
+
* Reset the singleton instance (useful for testing)
|
|
5090
5312
|
*/
|
|
5091
|
-
|
|
5092
|
-
|
|
5313
|
+
static resetInstance() {
|
|
5314
|
+
if (AIContextManager._instance) {
|
|
5315
|
+
AIContextManager._instance.destroy();
|
|
5316
|
+
AIContextManager._instance = null;
|
|
5317
|
+
}
|
|
5093
5318
|
}
|
|
5319
|
+
// ===== Configuration =====
|
|
5094
5320
|
/**
|
|
5095
|
-
*
|
|
5096
|
-
*
|
|
5321
|
+
* Set the chat client factory for creating chat clients for summarization
|
|
5322
|
+
* Required for compaction to work
|
|
5097
5323
|
*/
|
|
5098
|
-
|
|
5099
|
-
|
|
5324
|
+
setChatClientFactory(factory) {
|
|
5325
|
+
this.chatClientFactory = factory;
|
|
5100
5326
|
}
|
|
5101
5327
|
/**
|
|
5102
|
-
*
|
|
5328
|
+
* Update configuration
|
|
5103
5329
|
*/
|
|
5104
|
-
|
|
5105
|
-
this.
|
|
5106
|
-
this.
|
|
5330
|
+
setConfig(config) {
|
|
5331
|
+
const wasAutoCompactEnabled = this.config.enableAutoCompact;
|
|
5332
|
+
this.config = Object.assign(Object.assign({}, this.config), config);
|
|
5333
|
+
// Handle auto-compact state change
|
|
5334
|
+
if (config.enableAutoCompact !== undefined) {
|
|
5335
|
+
if (config.enableAutoCompact && !wasAutoCompactEnabled) {
|
|
5336
|
+
this.startAutoCompactCheck();
|
|
5337
|
+
}
|
|
5338
|
+
else if (!config.enableAutoCompact && wasAutoCompactEnabled) {
|
|
5339
|
+
this.stopAutoCompactCheck();
|
|
5340
|
+
}
|
|
5341
|
+
}
|
|
5107
5342
|
}
|
|
5343
|
+
// ===== Player Description =====
|
|
5108
5344
|
/**
|
|
5109
|
-
*
|
|
5345
|
+
* Set the player's description for AI context.
|
|
5346
|
+
* Used when generating reply predictions and for NPC context.
|
|
5347
|
+
* @param description Description of the player character
|
|
5110
5348
|
*/
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
parts.push(this.characterDesign);
|
|
5115
|
-
}
|
|
5116
|
-
if (this.memories.size > 0) {
|
|
5117
|
-
const memoryStrings = Array.from(this.memories.entries())
|
|
5118
|
-
.map(([name, content]) => `[${name}]: ${content}`);
|
|
5119
|
-
parts.push('Memories:\n' + memoryStrings.join('\n'));
|
|
5120
|
-
}
|
|
5121
|
-
return parts.join('\n\n');
|
|
5349
|
+
setPlayerDescription(description) {
|
|
5350
|
+
this.playerDescription = description;
|
|
5351
|
+
this.emit('playerDescriptionChanged', description);
|
|
5122
5352
|
}
|
|
5123
|
-
// ===== Reply Prediction =====
|
|
5124
5353
|
/**
|
|
5125
|
-
*
|
|
5354
|
+
* Get the current player description.
|
|
5355
|
+
* @returns The player description, or null if not set
|
|
5126
5356
|
*/
|
|
5127
|
-
|
|
5128
|
-
this.
|
|
5357
|
+
getPlayerDescription() {
|
|
5358
|
+
return this.playerDescription;
|
|
5129
5359
|
}
|
|
5130
5360
|
/**
|
|
5131
|
-
*
|
|
5361
|
+
* Clear the player description.
|
|
5132
5362
|
*/
|
|
5133
|
-
|
|
5134
|
-
this.
|
|
5363
|
+
clearPlayerDescription() {
|
|
5364
|
+
this.playerDescription = null;
|
|
5365
|
+
this.emit('playerDescriptionChanged', null);
|
|
5135
5366
|
}
|
|
5367
|
+
// ===== Player Prompt & Memory (for Reply Prediction) =====
|
|
5136
5368
|
/**
|
|
5137
|
-
*
|
|
5138
|
-
*
|
|
5139
|
-
*
|
|
5140
|
-
* @
|
|
5369
|
+
* Set the player's character prompt/persona.
|
|
5370
|
+
* This defines how the player character speaks and behaves.
|
|
5371
|
+
* Used when generating reply predictions to match the player's tone.
|
|
5372
|
+
* @param prompt The player character's persona/prompt
|
|
5141
5373
|
*/
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5374
|
+
setPlayerPrompt(prompt) {
|
|
5375
|
+
this.playerPrompt = prompt;
|
|
5376
|
+
}
|
|
5377
|
+
/**
|
|
5378
|
+
* Get the current player prompt.
|
|
5379
|
+
* @returns The player prompt, or null if not set
|
|
5380
|
+
*/
|
|
5381
|
+
getPlayerPrompt() {
|
|
5382
|
+
return this.playerPrompt;
|
|
5383
|
+
}
|
|
5384
|
+
/**
|
|
5385
|
+
* Set or update a memory for the player character.
|
|
5386
|
+
* Memories are appended to the player prompt to form the full player context.
|
|
5387
|
+
* Set memoryContent to null or empty to remove the memory.
|
|
5388
|
+
* @param memoryName The name/key of the memory
|
|
5389
|
+
* @param memoryContent The content of the memory. Null or empty to remove.
|
|
5390
|
+
*/
|
|
5391
|
+
setPlayerMemory(memoryName, memoryContent) {
|
|
5392
|
+
if (!memoryName) {
|
|
5393
|
+
this.logger.warn('Memory name cannot be empty');
|
|
5394
|
+
return;
|
|
5148
5395
|
}
|
|
5149
|
-
|
|
5150
|
-
//
|
|
5151
|
-
|
|
5152
|
-
.reverse()
|
|
5153
|
-
.find(m => m.role === 'assistant')) === null || _a === void 0 ? void 0 : _a.content;
|
|
5154
|
-
if (!lastNpcMessage) {
|
|
5155
|
-
this.logger.info('No NPC message found to generate predictions from');
|
|
5156
|
-
return [];
|
|
5157
|
-
}
|
|
5158
|
-
// Build recent history (last 6 non-system messages)
|
|
5159
|
-
const recentHistory = this.history
|
|
5160
|
-
.filter(m => m.role !== 'system')
|
|
5161
|
-
.slice(-6)
|
|
5162
|
-
.map(m => `${m.role}: ${m.content}`);
|
|
5163
|
-
// Build prompt for prediction generation
|
|
5164
|
-
const prompt = `Based on the conversation history below, generate exactly ${predictionNum} natural and contextually appropriate responses that the player might say next.
|
|
5165
|
-
|
|
5166
|
-
Context:
|
|
5167
|
-
- This is a conversation between a player and an NPC in a game
|
|
5168
|
-
- The NPC just said: "${lastNpcMessage}"
|
|
5169
|
-
|
|
5170
|
-
Conversation history:
|
|
5171
|
-
${recentHistory.join('\n')}
|
|
5172
|
-
|
|
5173
|
-
Requirements:
|
|
5174
|
-
1. Each response should be 1-2 sentences maximum
|
|
5175
|
-
2. Responses should be diverse in tone and intent
|
|
5176
|
-
3. Include a mix of questions, statements, and action-oriented responses
|
|
5177
|
-
4. Responses should feel natural for a player character
|
|
5178
|
-
|
|
5179
|
-
Output ONLY a JSON array of ${predictionNum} strings, nothing else:
|
|
5180
|
-
["response1", "response2", "response3", "response4"]`;
|
|
5181
|
-
const result = await this.chatClient.textGeneration({
|
|
5182
|
-
messages: [{ role: 'user', content: prompt }],
|
|
5183
|
-
temperature: 0.8,
|
|
5184
|
-
model: this.fastModel,
|
|
5185
|
-
});
|
|
5186
|
-
if (!result.content) {
|
|
5187
|
-
this.logger.warn('Failed to generate predictions: empty response');
|
|
5188
|
-
return [];
|
|
5189
|
-
}
|
|
5190
|
-
// Parse JSON response
|
|
5191
|
-
const predictions = this.parsePredictionsFromJson(result.content, predictionNum);
|
|
5192
|
-
if (predictions.length > 0) {
|
|
5193
|
-
this.emit('replyPredictions', predictions);
|
|
5194
|
-
}
|
|
5195
|
-
return predictions;
|
|
5396
|
+
if (!memoryContent) {
|
|
5397
|
+
// Remove memory if content is null or empty
|
|
5398
|
+
this.playerMemories.delete(memoryName);
|
|
5196
5399
|
}
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5400
|
+
else {
|
|
5401
|
+
// Add or update memory
|
|
5402
|
+
this.playerMemories.set(memoryName, memoryContent);
|
|
5200
5403
|
}
|
|
5201
5404
|
}
|
|
5202
5405
|
/**
|
|
5203
|
-
*
|
|
5406
|
+
* Get a specific player memory by name.
|
|
5407
|
+
* @param memoryName The name of the memory to retrieve
|
|
5408
|
+
* @returns The memory content, or undefined if not found
|
|
5204
5409
|
*/
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5410
|
+
getPlayerMemory(memoryName) {
|
|
5411
|
+
return this.playerMemories.get(memoryName);
|
|
5412
|
+
}
|
|
5413
|
+
/**
|
|
5414
|
+
* Get all player memory names currently stored.
|
|
5415
|
+
* @returns Array of memory names
|
|
5416
|
+
*/
|
|
5417
|
+
getPlayerMemoryNames() {
|
|
5418
|
+
return Array.from(this.playerMemories.keys());
|
|
5419
|
+
}
|
|
5420
|
+
/**
|
|
5421
|
+
* Clear all player memories (but keep player prompt).
|
|
5422
|
+
*/
|
|
5423
|
+
clearPlayerMemories() {
|
|
5424
|
+
this.playerMemories.clear();
|
|
5425
|
+
}
|
|
5426
|
+
/**
|
|
5427
|
+
* Build the complete player context from PlayerPrompt + PlayerMemories.
|
|
5428
|
+
* Used by NPCClient for generating reply predictions.
|
|
5429
|
+
* @returns The combined player context string, or null if no context is set
|
|
5430
|
+
*/
|
|
5431
|
+
buildPlayerContext() {
|
|
5432
|
+
const parts = [];
|
|
5433
|
+
if (this.playerPrompt) {
|
|
5434
|
+
parts.push(this.playerPrompt);
|
|
5222
5435
|
}
|
|
5223
|
-
|
|
5224
|
-
this.
|
|
5225
|
-
|
|
5436
|
+
if (this.playerMemories.size > 0) {
|
|
5437
|
+
const memoryStrings = Array.from(this.playerMemories.entries())
|
|
5438
|
+
.map(([name, content]) => `[${name}]: ${content}`);
|
|
5439
|
+
parts.push('Player Memories:\n' + memoryStrings.join('\n'));
|
|
5440
|
+
}
|
|
5441
|
+
if (parts.length === 0) {
|
|
5442
|
+
return null;
|
|
5226
5443
|
}
|
|
5444
|
+
return parts.join('\n\n');
|
|
5227
5445
|
}
|
|
5446
|
+
// ===== NPC Tracking =====
|
|
5228
5447
|
/**
|
|
5229
|
-
*
|
|
5448
|
+
* Register an NPC for context management.
|
|
5449
|
+
* @param npc The NPC client to register
|
|
5230
5450
|
*/
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
if (/^\d+\./.test(cleaned)) {
|
|
5241
|
-
cleaned = cleaned.replace(/^\d+\.\s*/, '');
|
|
5242
|
-
}
|
|
5243
|
-
else if (cleaned.startsWith('- ')) {
|
|
5244
|
-
cleaned = cleaned.substring(2);
|
|
5245
|
-
}
|
|
5246
|
-
// Remove surrounding quotes
|
|
5247
|
-
if (cleaned.startsWith('"') && cleaned.endsWith('"')) {
|
|
5248
|
-
cleaned = cleaned.slice(1, -1);
|
|
5249
|
-
}
|
|
5250
|
-
// Remove trailing comma
|
|
5251
|
-
if (cleaned.endsWith(',')) {
|
|
5252
|
-
cleaned = cleaned.slice(0, -1).trim();
|
|
5253
|
-
}
|
|
5254
|
-
if (cleaned && predictions.length < expectedCount) {
|
|
5255
|
-
predictions.push(cleaned);
|
|
5256
|
-
}
|
|
5451
|
+
registerNpc(npc) {
|
|
5452
|
+
if (!npc)
|
|
5453
|
+
return;
|
|
5454
|
+
if (!this.npcStates.has(npc)) {
|
|
5455
|
+
this.npcStates.set(npc, {
|
|
5456
|
+
lastConversationTime: new Date(),
|
|
5457
|
+
isCompacted: false,
|
|
5458
|
+
compactionCount: 0,
|
|
5459
|
+
});
|
|
5257
5460
|
}
|
|
5258
|
-
return predictions;
|
|
5259
5461
|
}
|
|
5260
5462
|
/**
|
|
5261
|
-
*
|
|
5463
|
+
* Unregister an NPC (call when NPC is destroyed/removed).
|
|
5464
|
+
* @param npc The NPC client to unregister
|
|
5262
5465
|
*/
|
|
5263
|
-
|
|
5264
|
-
if (!
|
|
5466
|
+
unregisterNpc(npc) {
|
|
5467
|
+
if (!npc)
|
|
5265
5468
|
return;
|
|
5266
|
-
|
|
5267
|
-
this.generateReplyPredictions().catch(err => {
|
|
5268
|
-
this.logger.error('Background prediction generation failed:', err);
|
|
5269
|
-
});
|
|
5469
|
+
this.npcStates.delete(npc);
|
|
5270
5470
|
}
|
|
5271
|
-
// ===== Main API - Talk Methods =====
|
|
5272
5471
|
/**
|
|
5273
|
-
*
|
|
5472
|
+
* Record that a conversation occurred with an NPC.
|
|
5473
|
+
* Called after each Talk() exchange.
|
|
5474
|
+
* @param npc The NPC client that had a conversation
|
|
5274
5475
|
*/
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
this.history.push(userMessage);
|
|
5281
|
-
// Build messages array with system prompt
|
|
5282
|
-
const messages = [
|
|
5283
|
-
{ role: 'system', content: this.buildSystemPrompt() },
|
|
5284
|
-
...this.history,
|
|
5285
|
-
];
|
|
5286
|
-
// Generate response
|
|
5287
|
-
const result = await this.chatClient.textGeneration({
|
|
5288
|
-
messages,
|
|
5289
|
-
temperature: this.temperature,
|
|
5290
|
-
});
|
|
5291
|
-
// Add assistant response to history
|
|
5292
|
-
const assistantMessage = { role: 'assistant', content: result.content };
|
|
5293
|
-
this.history.push(assistantMessage);
|
|
5294
|
-
// Trim history if needed
|
|
5295
|
-
this.trimHistory();
|
|
5296
|
-
this.emit('response', result.content);
|
|
5297
|
-
// Trigger reply prediction generation (fire and forget)
|
|
5298
|
-
this.triggerReplyPrediction();
|
|
5299
|
-
return result.content;
|
|
5300
|
-
}
|
|
5301
|
-
finally {
|
|
5302
|
-
this._isTalking = false;
|
|
5476
|
+
recordConversation(npc) {
|
|
5477
|
+
if (!npc)
|
|
5478
|
+
return;
|
|
5479
|
+
if (!this.npcStates.has(npc)) {
|
|
5480
|
+
this.registerNpc(npc);
|
|
5303
5481
|
}
|
|
5482
|
+
const state = this.npcStates.get(npc);
|
|
5483
|
+
state.lastConversationTime = new Date();
|
|
5484
|
+
state.isCompacted = false; // Reset compaction flag on new conversation
|
|
5304
5485
|
}
|
|
5305
5486
|
/**
|
|
5306
|
-
*
|
|
5487
|
+
* Get all registered NPCs
|
|
5307
5488
|
*/
|
|
5308
|
-
|
|
5309
|
-
this.
|
|
5310
|
-
try {
|
|
5311
|
-
// Add user message to history
|
|
5312
|
-
const userMessage = { role: 'user', content: message };
|
|
5313
|
-
this.history.push(userMessage);
|
|
5314
|
-
// Build messages array with system prompt
|
|
5315
|
-
const messages = [
|
|
5316
|
-
{ role: 'system', content: this.buildSystemPrompt() },
|
|
5317
|
-
...this.history,
|
|
5318
|
-
];
|
|
5319
|
-
// Generate response
|
|
5320
|
-
await this.chatClient.textGenerationStream({
|
|
5321
|
-
messages,
|
|
5322
|
-
temperature: this.temperature,
|
|
5323
|
-
onChunk,
|
|
5324
|
-
onComplete: (fullText) => {
|
|
5325
|
-
this._isTalking = false;
|
|
5326
|
-
// Add assistant response to history
|
|
5327
|
-
const assistantMessage = { role: 'assistant', content: fullText };
|
|
5328
|
-
this.history.push(assistantMessage);
|
|
5329
|
-
// Trim history if needed
|
|
5330
|
-
this.trimHistory();
|
|
5331
|
-
this.emit('response', fullText);
|
|
5332
|
-
// Trigger reply prediction generation (fire and forget)
|
|
5333
|
-
this.triggerReplyPrediction();
|
|
5334
|
-
if (onComplete) {
|
|
5335
|
-
onComplete(fullText);
|
|
5336
|
-
}
|
|
5337
|
-
},
|
|
5338
|
-
});
|
|
5339
|
-
}
|
|
5340
|
-
catch (error) {
|
|
5341
|
-
this._isTalking = false;
|
|
5342
|
-
throw error;
|
|
5343
|
-
}
|
|
5489
|
+
getRegisteredNpcs() {
|
|
5490
|
+
return Array.from(this.npcStates.keys());
|
|
5344
5491
|
}
|
|
5345
5492
|
/**
|
|
5346
|
-
*
|
|
5347
|
-
* @deprecated Use talkWithActions instead for NPC decision-making with actions
|
|
5493
|
+
* Get the conversation state for an NPC
|
|
5348
5494
|
*/
|
|
5349
|
-
|
|
5350
|
-
this.
|
|
5351
|
-
// Add user message to history
|
|
5352
|
-
const userMessage = { role: 'user', content: message };
|
|
5353
|
-
this.history.push(userMessage);
|
|
5354
|
-
// Generate structured response
|
|
5355
|
-
const result = await this.chatClient.generateStructured({
|
|
5356
|
-
schemaName,
|
|
5357
|
-
prompt: message,
|
|
5358
|
-
messages: [{ role: 'system', content: this.buildSystemPrompt() }, ...this.history],
|
|
5359
|
-
temperature: this.temperature,
|
|
5360
|
-
});
|
|
5361
|
-
// Add a text representation to history
|
|
5362
|
-
const assistantMessage = {
|
|
5363
|
-
role: 'assistant',
|
|
5364
|
-
content: JSON.stringify(result),
|
|
5365
|
-
};
|
|
5366
|
-
this.history.push(assistantMessage);
|
|
5367
|
-
this.trimHistory();
|
|
5368
|
-
return result;
|
|
5495
|
+
getNpcState(npc) {
|
|
5496
|
+
return this.npcStates.get(npc);
|
|
5369
5497
|
}
|
|
5498
|
+
// ===== Auto Compaction =====
|
|
5370
5499
|
/**
|
|
5371
|
-
*
|
|
5372
|
-
* @param
|
|
5373
|
-
* @
|
|
5374
|
-
* @returns Response containing text and any action calls
|
|
5500
|
+
* Check if an NPC is eligible for compaction.
|
|
5501
|
+
* @param npc The NPC to check
|
|
5502
|
+
* @returns True if eligible for compaction
|
|
5375
5503
|
*/
|
|
5376
|
-
|
|
5377
|
-
|
|
5504
|
+
isEligibleForCompaction(npc) {
|
|
5505
|
+
if (!npc)
|
|
5506
|
+
return false;
|
|
5507
|
+
const state = this.npcStates.get(npc);
|
|
5508
|
+
if (!state)
|
|
5509
|
+
return false;
|
|
5510
|
+
// Check if already compacted since last conversation
|
|
5511
|
+
if (state.isCompacted)
|
|
5512
|
+
return false;
|
|
5513
|
+
// Check message count
|
|
5514
|
+
const history = npc.getHistory();
|
|
5515
|
+
const nonSystemMessages = history.filter(m => m.role !== 'system').length;
|
|
5516
|
+
if (nonSystemMessages < this.config.autoCompactMinMessages)
|
|
5517
|
+
return false;
|
|
5518
|
+
// Check time since last conversation
|
|
5519
|
+
const timeSinceLastConversation = (Date.now() - state.lastConversationTime.getTime()) / 1000;
|
|
5520
|
+
if (timeSinceLastConversation < this.config.autoCompactTimeoutSeconds)
|
|
5521
|
+
return false;
|
|
5522
|
+
return true;
|
|
5523
|
+
}
|
|
5524
|
+
/**
|
|
5525
|
+
* Manually trigger conversation compaction for a specific NPC.
|
|
5526
|
+
* Summarizes the conversation history and stores it as a memory.
|
|
5527
|
+
* @param npc The NPC to compact
|
|
5528
|
+
* @returns True if compaction succeeded
|
|
5529
|
+
*/
|
|
5530
|
+
async compactConversation(npc) {
|
|
5531
|
+
if (!npc) {
|
|
5532
|
+
this.logger.warn('Cannot compact: NPC is null');
|
|
5533
|
+
return false;
|
|
5534
|
+
}
|
|
5535
|
+
if (!this.chatClientFactory) {
|
|
5536
|
+
this.logger.error('Cannot compact: No chat client factory set. Call setChatClientFactory() first.');
|
|
5537
|
+
return false;
|
|
5538
|
+
}
|
|
5539
|
+
const history = npc.getHistory();
|
|
5540
|
+
const nonSystemMessages = history.filter(m => m.role !== 'system');
|
|
5541
|
+
if (nonSystemMessages.length < 2) {
|
|
5542
|
+
this.logger.info('Skipping compaction: not enough messages');
|
|
5543
|
+
return false;
|
|
5544
|
+
}
|
|
5378
5545
|
try {
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5546
|
+
this.logger.info(`Starting compaction (${nonSystemMessages.length} messages)`);
|
|
5547
|
+
// Build conversation text for summarization
|
|
5548
|
+
const conversationText = nonSystemMessages
|
|
5549
|
+
.map(m => `${m.role}: ${m.content}`)
|
|
5550
|
+
.join('\n');
|
|
5551
|
+
// Create summarization prompt
|
|
5552
|
+
const summaryPrompt = `Summarize the following conversation concisely. Focus on:
|
|
5553
|
+
1. Key topics discussed
|
|
5554
|
+
2. Important information exchanged
|
|
5555
|
+
3. Any decisions or commitments made
|
|
5556
|
+
4. The emotional tone
|
|
5557
|
+
|
|
5558
|
+
Keep the summary under 200 words. Write in third person.
|
|
5559
|
+
|
|
5560
|
+
Conversation:
|
|
5561
|
+
${conversationText}`;
|
|
5562
|
+
// Use chat client for summarization
|
|
5563
|
+
const chatClient = this.chatClientFactory();
|
|
5564
|
+
const result = await chatClient.textGeneration({
|
|
5565
|
+
messages: [{ role: 'user', content: summaryPrompt }],
|
|
5566
|
+
temperature: 0.5,
|
|
5567
|
+
model: this.config.fastModel || undefined,
|
|
5397
5568
|
});
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
};
|
|
5404
|
-
// Extract tool calls if any
|
|
5405
|
-
if (result.tool_calls) {
|
|
5406
|
-
response.actionCalls = result.tool_calls.map(tc => ({
|
|
5407
|
-
id: tc.id,
|
|
5408
|
-
actionName: tc.function.name,
|
|
5409
|
-
arguments: this.parseToolArguments(tc.function.arguments),
|
|
5410
|
-
}));
|
|
5411
|
-
response.hasActions = response.actionCalls.length > 0;
|
|
5569
|
+
if (!result.content) {
|
|
5570
|
+
const error = 'Empty response from summarization';
|
|
5571
|
+
this.logger.error(`Compaction failed: ${error}`);
|
|
5572
|
+
this.emit('compactionFailed', npc, error);
|
|
5573
|
+
return false;
|
|
5412
5574
|
}
|
|
5413
|
-
//
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
this.emit('response', response.text);
|
|
5422
|
-
if (response.hasActions) {
|
|
5423
|
-
this.emit('actions', response.actionCalls);
|
|
5575
|
+
// Clear history and add summary as memory
|
|
5576
|
+
npc.clearHistory();
|
|
5577
|
+
npc.setMemory('PreviousConversationSummary', result.content);
|
|
5578
|
+
// Update state
|
|
5579
|
+
const state = this.npcStates.get(npc);
|
|
5580
|
+
if (state) {
|
|
5581
|
+
state.isCompacted = true;
|
|
5582
|
+
state.compactionCount++;
|
|
5424
5583
|
}
|
|
5425
|
-
|
|
5426
|
-
this.
|
|
5427
|
-
return
|
|
5584
|
+
this.logger.info(`Compaction completed. Summary: ${result.content.substring(0, 100)}...`);
|
|
5585
|
+
this.emit('npcCompacted', npc);
|
|
5586
|
+
return true;
|
|
5428
5587
|
}
|
|
5429
|
-
|
|
5430
|
-
|
|
5588
|
+
catch (error) {
|
|
5589
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5590
|
+
this.logger.error(`Compaction error: ${errorMessage}`);
|
|
5591
|
+
this.emit('compactionFailed', npc, errorMessage);
|
|
5592
|
+
return false;
|
|
5431
5593
|
}
|
|
5432
5594
|
}
|
|
5433
5595
|
/**
|
|
5434
|
-
*
|
|
5435
|
-
*
|
|
5596
|
+
* Compact all registered NPCs that meet the eligibility criteria.
|
|
5597
|
+
* @returns Number of NPCs successfully compacted
|
|
5436
5598
|
*/
|
|
5437
|
-
async
|
|
5438
|
-
this.
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
const userMessage = { role: 'user', content: message };
|
|
5442
|
-
this.history.push(userMessage);
|
|
5443
|
-
// Convert NpcActions to ChatTools
|
|
5444
|
-
const tools = actions
|
|
5445
|
-
.filter(a => a && a.enabled !== false)
|
|
5446
|
-
.map(a => npcActionToTool(a));
|
|
5447
|
-
// Build messages array with system prompt
|
|
5448
|
-
const messages = [
|
|
5449
|
-
{ role: 'system', content: this.buildSystemPrompt() },
|
|
5450
|
-
...this.history,
|
|
5451
|
-
];
|
|
5452
|
-
// Generate response with tools (streaming)
|
|
5453
|
-
await this.chatClient.textGenerationWithToolsStream({
|
|
5454
|
-
messages,
|
|
5455
|
-
temperature: this.temperature,
|
|
5456
|
-
tools,
|
|
5457
|
-
tool_choice: 'auto',
|
|
5458
|
-
onChunk,
|
|
5459
|
-
onComplete: (result) => {
|
|
5460
|
-
this._isTalking = false;
|
|
5461
|
-
// Build response
|
|
5462
|
-
const response = {
|
|
5463
|
-
text: result.content || '',
|
|
5464
|
-
actionCalls: [],
|
|
5465
|
-
hasActions: false,
|
|
5466
|
-
};
|
|
5467
|
-
// Extract tool calls if any
|
|
5468
|
-
if (result.tool_calls) {
|
|
5469
|
-
response.actionCalls = result.tool_calls.map(tc => ({
|
|
5470
|
-
id: tc.id,
|
|
5471
|
-
actionName: tc.function.name,
|
|
5472
|
-
arguments: this.parseToolArguments(tc.function.arguments),
|
|
5473
|
-
}));
|
|
5474
|
-
response.hasActions = response.actionCalls.length > 0;
|
|
5475
|
-
}
|
|
5476
|
-
// Add assistant response to history
|
|
5477
|
-
const assistantMessage = {
|
|
5478
|
-
role: 'assistant',
|
|
5479
|
-
content: response.text,
|
|
5480
|
-
tool_calls: result.tool_calls,
|
|
5481
|
-
};
|
|
5482
|
-
this.history.push(assistantMessage);
|
|
5483
|
-
this.trimHistory();
|
|
5484
|
-
this.emit('response', response.text);
|
|
5485
|
-
if (response.hasActions) {
|
|
5486
|
-
this.emit('actions', response.actionCalls);
|
|
5487
|
-
}
|
|
5488
|
-
// Trigger reply prediction generation (fire and forget)
|
|
5489
|
-
this.triggerReplyPrediction();
|
|
5490
|
-
if (onComplete) {
|
|
5491
|
-
onComplete(response);
|
|
5492
|
-
}
|
|
5493
|
-
},
|
|
5494
|
-
});
|
|
5599
|
+
async compactAllEligible() {
|
|
5600
|
+
const eligibleNpcs = Array.from(this.npcStates.keys()).filter(npc => this.isEligibleForCompaction(npc));
|
|
5601
|
+
if (eligibleNpcs.length === 0) {
|
|
5602
|
+
return 0;
|
|
5495
5603
|
}
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5604
|
+
this.logger.info(`Compacting ${eligibleNpcs.length} eligible NPCs`);
|
|
5605
|
+
let successCount = 0;
|
|
5606
|
+
for (const npc of eligibleNpcs) {
|
|
5607
|
+
const success = await this.compactConversation(npc);
|
|
5608
|
+
if (success)
|
|
5609
|
+
successCount++;
|
|
5499
5610
|
}
|
|
5611
|
+
return successCount;
|
|
5500
5612
|
}
|
|
5501
|
-
// =====
|
|
5613
|
+
// ===== Auto Compact Timer =====
|
|
5502
5614
|
/**
|
|
5503
|
-
*
|
|
5504
|
-
* Call this after executing actions to let the NPC know the results
|
|
5615
|
+
* Start the auto-compact check timer
|
|
5505
5616
|
*/
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
this.
|
|
5509
|
-
role: 'tool',
|
|
5510
|
-
tool_call_id: callId,
|
|
5511
|
-
content: result,
|
|
5512
|
-
});
|
|
5617
|
+
startAutoCompactCheck() {
|
|
5618
|
+
if (this.autoCompactTimer) {
|
|
5619
|
+
this.stopAutoCompactCheck();
|
|
5513
5620
|
}
|
|
5621
|
+
this.autoCompactTimer = setInterval(() => {
|
|
5622
|
+
this.runAutoCompactCheck();
|
|
5623
|
+
}, this.config.autoCompactCheckInterval);
|
|
5514
5624
|
}
|
|
5515
5625
|
/**
|
|
5516
|
-
*
|
|
5626
|
+
* Stop the auto-compact check timer
|
|
5517
5627
|
*/
|
|
5518
|
-
|
|
5519
|
-
this.
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
});
|
|
5628
|
+
stopAutoCompactCheck() {
|
|
5629
|
+
if (this.autoCompactTimer) {
|
|
5630
|
+
clearInterval(this.autoCompactTimer);
|
|
5631
|
+
this.autoCompactTimer = null;
|
|
5632
|
+
}
|
|
5524
5633
|
}
|
|
5525
5634
|
/**
|
|
5526
|
-
*
|
|
5635
|
+
* Run a single auto-compact check
|
|
5527
5636
|
*/
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
return
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5637
|
+
async runAutoCompactCheck() {
|
|
5638
|
+
if (!this.config.enableAutoCompact)
|
|
5639
|
+
return;
|
|
5640
|
+
const eligibleNpcs = Array.from(this.npcStates.keys()).filter(npc => this.isEligibleForCompaction(npc));
|
|
5641
|
+
for (const npc of eligibleNpcs) {
|
|
5642
|
+
// Fire and forget - don't block
|
|
5643
|
+
this.compactConversation(npc).catch(err => {
|
|
5644
|
+
this.logger.error('Auto-compact error:', err);
|
|
5645
|
+
});
|
|
5534
5646
|
}
|
|
5535
5647
|
}
|
|
5536
|
-
// =====
|
|
5648
|
+
// ===== Lifecycle =====
|
|
5537
5649
|
/**
|
|
5538
|
-
*
|
|
5650
|
+
* Enable auto-compaction
|
|
5539
5651
|
*/
|
|
5540
|
-
|
|
5541
|
-
|
|
5652
|
+
enableAutoCompact() {
|
|
5653
|
+
this.config.enableAutoCompact = true;
|
|
5654
|
+
this.startAutoCompactCheck();
|
|
5542
5655
|
}
|
|
5543
5656
|
/**
|
|
5544
|
-
*
|
|
5657
|
+
* Disable auto-compaction
|
|
5545
5658
|
*/
|
|
5546
|
-
|
|
5547
|
-
|
|
5659
|
+
disableAutoCompact() {
|
|
5660
|
+
this.config.enableAutoCompact = false;
|
|
5661
|
+
this.stopAutoCompactCheck();
|
|
5548
5662
|
}
|
|
5549
5663
|
/**
|
|
5550
|
-
*
|
|
5551
|
-
* The character design and memories will be preserved.
|
|
5664
|
+
* Clean up resources
|
|
5552
5665
|
*/
|
|
5553
|
-
|
|
5666
|
+
destroy() {
|
|
5667
|
+
this.stopAutoCompactCheck();
|
|
5668
|
+
this.npcStates.clear();
|
|
5669
|
+
this.playerDescription = null;
|
|
5670
|
+
this.playerPrompt = null;
|
|
5671
|
+
this.playerMemories.clear();
|
|
5672
|
+
this.removeAllListeners();
|
|
5673
|
+
}
|
|
5674
|
+
}
|
|
5675
|
+
AIContextManager._instance = null;
|
|
5676
|
+
/**
|
|
5677
|
+
* Default AIContextManager instance
|
|
5678
|
+
* Can be used as a global context manager
|
|
5679
|
+
*/
|
|
5680
|
+
const defaultContextManager = AIContextManager.getInstance();
|
|
5681
|
+
|
|
5682
|
+
/**
|
|
5683
|
+
* NPC Client for simplified conversation management
|
|
5684
|
+
* Automatically handles conversation history
|
|
5685
|
+
*
|
|
5686
|
+
* Key Features:
|
|
5687
|
+
* - Call talk() for all interactions - actions are handled automatically
|
|
5688
|
+
* - Memory system for persistent NPC context
|
|
5689
|
+
* - Reply prediction for suggesting player responses
|
|
5690
|
+
* - Automatic conversation history management
|
|
5691
|
+
*/
|
|
5692
|
+
class NPCClient extends EventEmitter {
|
|
5693
|
+
constructor(chatClient, config) {
|
|
5694
|
+
var _a, _b, _c;
|
|
5695
|
+
super();
|
|
5696
|
+
this._isTalking = false;
|
|
5697
|
+
this.logger = Logger.getLogger('NPCClient');
|
|
5698
|
+
this.chatClient = chatClient;
|
|
5699
|
+
// Support both characterDesign and legacy systemPrompt
|
|
5700
|
+
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.';
|
|
5701
|
+
this.temperature = (_a = config === null || config === void 0 ? void 0 : config.temperature) !== null && _a !== void 0 ? _a : 0.7;
|
|
5702
|
+
this.maxHistoryLength = (config === null || config === void 0 ? void 0 : config.maxHistoryLength) || 50;
|
|
5703
|
+
this.generateReplyPrediction = (_b = config === null || config === void 0 ? void 0 : config.generateReplyPrediction) !== null && _b !== void 0 ? _b : false;
|
|
5704
|
+
this.predictionCount = Math.max(2, Math.min(6, (_c = config === null || config === void 0 ? void 0 : config.predictionCount) !== null && _c !== void 0 ? _c : 4));
|
|
5705
|
+
this.fastModel = config === null || config === void 0 ? void 0 : config.fastModel;
|
|
5554
5706
|
this.history = [];
|
|
5555
|
-
this.
|
|
5707
|
+
this.memories = new Map();
|
|
5556
5708
|
}
|
|
5709
|
+
// ===== State Properties =====
|
|
5557
5710
|
/**
|
|
5558
|
-
*
|
|
5559
|
-
* @returns true if reverted, false if not enough history
|
|
5711
|
+
* Whether the NPC is currently processing a request
|
|
5560
5712
|
*/
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5713
|
+
get isTalking() {
|
|
5714
|
+
return this._isTalking;
|
|
5715
|
+
}
|
|
5716
|
+
// ===== Character Design & Memory System =====
|
|
5717
|
+
/**
|
|
5718
|
+
* Set the character design for the NPC.
|
|
5719
|
+
* The system prompt is composed of CharacterDesign + all Memories.
|
|
5720
|
+
*/
|
|
5721
|
+
setCharacterDesign(design) {
|
|
5722
|
+
this.characterDesign = design;
|
|
5723
|
+
}
|
|
5724
|
+
/**
|
|
5725
|
+
* Get the current character design
|
|
5726
|
+
*/
|
|
5727
|
+
getCharacterDesign() {
|
|
5728
|
+
return this.characterDesign;
|
|
5729
|
+
}
|
|
5730
|
+
/**
|
|
5731
|
+
* @deprecated Use setCharacterDesign instead.
|
|
5732
|
+
* This method is kept for backwards compatibility.
|
|
5733
|
+
*/
|
|
5734
|
+
setSystemPrompt(prompt) {
|
|
5735
|
+
this.logger.warn('setSystemPrompt is deprecated. Use setCharacterDesign instead.');
|
|
5736
|
+
this.setCharacterDesign(prompt);
|
|
5737
|
+
}
|
|
5738
|
+
/**
|
|
5739
|
+
* @deprecated Use getCharacterDesign instead.
|
|
5740
|
+
* This method is kept for backwards compatibility.
|
|
5741
|
+
*/
|
|
5742
|
+
getSystemPrompt() {
|
|
5743
|
+
return this.buildSystemPrompt();
|
|
5744
|
+
}
|
|
5745
|
+
/**
|
|
5746
|
+
* Set or update a memory for the NPC.
|
|
5747
|
+
* Memories are appended to the character design to form the system prompt.
|
|
5748
|
+
* Set memoryContent to null or empty to remove the memory.
|
|
5749
|
+
* @param memoryName The name/key of the memory
|
|
5750
|
+
* @param memoryContent The content of the memory. Null or empty to remove.
|
|
5751
|
+
*/
|
|
5752
|
+
setMemory(memoryName, memoryContent) {
|
|
5753
|
+
if (!memoryName) {
|
|
5754
|
+
this.logger.warn('Memory name cannot be empty');
|
|
5755
|
+
return;
|
|
5756
|
+
}
|
|
5757
|
+
if (!memoryContent) {
|
|
5758
|
+
// Remove memory if content is null or empty
|
|
5759
|
+
if (this.memories.has(memoryName)) {
|
|
5760
|
+
this.memories.delete(memoryName);
|
|
5761
|
+
this.emit('memory_removed', memoryName);
|
|
5571
5762
|
}
|
|
5572
5763
|
}
|
|
5573
|
-
|
|
5574
|
-
//
|
|
5575
|
-
this.
|
|
5576
|
-
this.
|
|
5577
|
-
this.emit('history_reverted');
|
|
5578
|
-
return true;
|
|
5764
|
+
else {
|
|
5765
|
+
// Add or update memory
|
|
5766
|
+
this.memories.set(memoryName, memoryContent);
|
|
5767
|
+
this.emit('memory_set', memoryName, memoryContent);
|
|
5579
5768
|
}
|
|
5580
|
-
return false;
|
|
5581
5769
|
}
|
|
5582
5770
|
/**
|
|
5583
|
-
*
|
|
5584
|
-
* @param
|
|
5585
|
-
* @returns
|
|
5771
|
+
* Get a specific memory by name.
|
|
5772
|
+
* @param memoryName The name of the memory to retrieve
|
|
5773
|
+
* @returns The memory content, or undefined if not found
|
|
5586
5774
|
*/
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5775
|
+
getMemory(memoryName) {
|
|
5776
|
+
return this.memories.get(memoryName);
|
|
5777
|
+
}
|
|
5778
|
+
/**
|
|
5779
|
+
* Get all memory names currently stored.
|
|
5780
|
+
* @returns Array of memory names
|
|
5781
|
+
*/
|
|
5782
|
+
getMemoryNames() {
|
|
5783
|
+
return Array.from(this.memories.keys());
|
|
5784
|
+
}
|
|
5785
|
+
/**
|
|
5786
|
+
* Clear all memories (but keep character design).
|
|
5787
|
+
*/
|
|
5788
|
+
clearMemories() {
|
|
5789
|
+
this.memories.clear();
|
|
5790
|
+
this.emit('memories_cleared');
|
|
5791
|
+
}
|
|
5792
|
+
/**
|
|
5793
|
+
* Build the complete system prompt from CharacterDesign + Memories.
|
|
5794
|
+
*/
|
|
5795
|
+
buildSystemPrompt() {
|
|
5796
|
+
const parts = [];
|
|
5797
|
+
if (this.characterDesign) {
|
|
5798
|
+
parts.push(this.characterDesign);
|
|
5799
|
+
}
|
|
5800
|
+
if (this.memories.size > 0) {
|
|
5801
|
+
const memoryStrings = Array.from(this.memories.entries())
|
|
5802
|
+
.map(([name, content]) => `[${name}]: ${content}`);
|
|
5803
|
+
parts.push('Memories:\n' + memoryStrings.join('\n'));
|
|
5596
5804
|
}
|
|
5597
|
-
return
|
|
5805
|
+
return parts.join('\n\n');
|
|
5598
5806
|
}
|
|
5807
|
+
// ===== Reply Prediction =====
|
|
5599
5808
|
/**
|
|
5600
|
-
*
|
|
5601
|
-
* @deprecated Use revertHistory() or revertChatMessages() instead
|
|
5809
|
+
* Enable or disable automatic reply prediction
|
|
5602
5810
|
*/
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
this.history = this.history.slice(0, index + 1);
|
|
5606
|
-
this.emit('history_reverted', index);
|
|
5607
|
-
}
|
|
5811
|
+
setGenerateReplyPrediction(enabled) {
|
|
5812
|
+
this.generateReplyPrediction = enabled;
|
|
5608
5813
|
}
|
|
5609
5814
|
/**
|
|
5610
|
-
*
|
|
5815
|
+
* Set the number of predictions to generate
|
|
5611
5816
|
*/
|
|
5612
|
-
|
|
5613
|
-
this.
|
|
5614
|
-
this.trimHistory();
|
|
5817
|
+
setPredictionCount(count) {
|
|
5818
|
+
this.predictionCount = Math.max(2, Math.min(6, count));
|
|
5615
5819
|
}
|
|
5616
5820
|
/**
|
|
5617
|
-
*
|
|
5821
|
+
* Manually generate reply predictions based on current conversation.
|
|
5822
|
+
* Uses the fast model for quick generation.
|
|
5823
|
+
* @param tempPrompt Optional temporary prompt to influence the prediction style/tone
|
|
5824
|
+
* @param count Number of predictions to generate (default: uses predictionCount property)
|
|
5825
|
+
* @returns Array of predicted player replies, or empty array on failure
|
|
5618
5826
|
*/
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5827
|
+
async generateReplyPredictions(tempPrompt, count) {
|
|
5828
|
+
var _a;
|
|
5829
|
+
const predictionNum = count !== null && count !== void 0 ? count : this.predictionCount;
|
|
5830
|
+
if (this.history.length < 2) {
|
|
5831
|
+
this.logger.info('Not enough conversation history to generate predictions');
|
|
5832
|
+
return [];
|
|
5833
|
+
}
|
|
5834
|
+
try {
|
|
5835
|
+
// Get last NPC message
|
|
5836
|
+
const lastNpcMessage = (_a = [...this.history]
|
|
5837
|
+
.reverse()
|
|
5838
|
+
.find(m => m.role === 'assistant')) === null || _a === void 0 ? void 0 : _a.content;
|
|
5839
|
+
if (!lastNpcMessage) {
|
|
5840
|
+
this.logger.info('No NPC message found to generate predictions from');
|
|
5841
|
+
return [];
|
|
5842
|
+
}
|
|
5843
|
+
// Build recent history (last 6 non-system messages)
|
|
5844
|
+
const recentHistory = this.history
|
|
5845
|
+
.filter(m => m.role !== 'system')
|
|
5846
|
+
.slice(-6)
|
|
5847
|
+
.map(m => `${m.role}: ${m.content}`);
|
|
5848
|
+
// Get player context from AIContextManager
|
|
5849
|
+
const contextManager = AIContextManager.getInstance();
|
|
5850
|
+
const playerContext = contextManager.buildPlayerContext();
|
|
5851
|
+
// Build player character section
|
|
5852
|
+
let playerCharacterSection = '';
|
|
5853
|
+
if (playerContext || tempPrompt) {
|
|
5854
|
+
playerCharacterSection = '\nPlayer Character:\n';
|
|
5855
|
+
if (playerContext) {
|
|
5856
|
+
playerCharacterSection += playerContext + '\n';
|
|
5857
|
+
}
|
|
5858
|
+
if (tempPrompt) {
|
|
5859
|
+
playerCharacterSection += `Additional guidance: ${tempPrompt}\n`;
|
|
5860
|
+
}
|
|
5861
|
+
}
|
|
5862
|
+
// Build prompt for prediction generation
|
|
5863
|
+
const prompt = `Based on the conversation history below, generate exactly ${predictionNum} natural and contextually appropriate responses that the player might say next.
|
|
5864
|
+
|
|
5865
|
+
Context:
|
|
5866
|
+
- This is a conversation between a player and an NPC in a game
|
|
5867
|
+
- The NPC just said: "${lastNpcMessage}"
|
|
5868
|
+
${playerCharacterSection}
|
|
5869
|
+
Conversation history:
|
|
5870
|
+
${recentHistory.join('\n')}
|
|
5871
|
+
|
|
5872
|
+
Requirements:
|
|
5873
|
+
1. Each response should be 1-2 sentences maximum
|
|
5874
|
+
2. Responses should be diverse in tone and intent
|
|
5875
|
+
3. Include a mix of questions, statements, and action-oriented responses
|
|
5876
|
+
4. Responses should feel natural for the player character${playerContext || tempPrompt ? ' and match their personality/tone' : ''}
|
|
5877
|
+
|
|
5878
|
+
Output ONLY a JSON array of ${predictionNum} strings, nothing else:
|
|
5879
|
+
["response1", "response2", "response3", "response4"]`;
|
|
5880
|
+
const result = await this.chatClient.textGeneration({
|
|
5881
|
+
messages: [{ role: 'user', content: prompt }],
|
|
5882
|
+
temperature: 0.8,
|
|
5883
|
+
model: this.fastModel,
|
|
5884
|
+
});
|
|
5885
|
+
if (!result.content) {
|
|
5886
|
+
this.logger.warn('Failed to generate predictions: empty response');
|
|
5887
|
+
return [];
|
|
5888
|
+
}
|
|
5889
|
+
// Parse JSON response
|
|
5890
|
+
const predictions = this.parsePredictionsFromJson(result.content, predictionNum);
|
|
5891
|
+
if (predictions.length > 0) {
|
|
5892
|
+
this.emit('replyPredictions', predictions);
|
|
5893
|
+
}
|
|
5894
|
+
return predictions;
|
|
5895
|
+
}
|
|
5896
|
+
catch (error) {
|
|
5897
|
+
this.logger.error('Error generating predictions:', error);
|
|
5898
|
+
return [];
|
|
5623
5899
|
}
|
|
5624
|
-
this.appendMessage({ role: role, content });
|
|
5625
5900
|
}
|
|
5626
5901
|
/**
|
|
5627
|
-
*
|
|
5902
|
+
* Parse predictions from JSON array response
|
|
5628
5903
|
*/
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
//
|
|
5632
|
-
|
|
5904
|
+
parsePredictionsFromJson(response, expectedCount) {
|
|
5905
|
+
try {
|
|
5906
|
+
// Try to find JSON array in response
|
|
5907
|
+
const startIndex = response.indexOf('[');
|
|
5908
|
+
const endIndex = response.lastIndexOf(']');
|
|
5909
|
+
if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) {
|
|
5910
|
+
this.logger.warn('Could not find JSON array in prediction response');
|
|
5911
|
+
return this.extractPredictionsFromText(response, expectedCount);
|
|
5912
|
+
}
|
|
5913
|
+
const jsonArray = response.substring(startIndex, endIndex + 1);
|
|
5914
|
+
const parsed = JSON.parse(jsonArray);
|
|
5915
|
+
if (Array.isArray(parsed)) {
|
|
5916
|
+
return parsed
|
|
5917
|
+
.filter(item => typeof item === 'string' && item.trim())
|
|
5918
|
+
.slice(0, expectedCount);
|
|
5919
|
+
}
|
|
5920
|
+
return [];
|
|
5921
|
+
}
|
|
5922
|
+
catch (error) {
|
|
5923
|
+
this.logger.warn('Failed to parse predictions JSON:', error);
|
|
5924
|
+
return this.extractPredictionsFromText(response, expectedCount);
|
|
5633
5925
|
}
|
|
5634
5926
|
}
|
|
5635
|
-
// ===== Save/Load =====
|
|
5636
5927
|
/**
|
|
5637
|
-
*
|
|
5638
|
-
* Includes characterDesign, memories, and history.
|
|
5928
|
+
* Fallback: Extract predictions from text when JSON parsing fails
|
|
5639
5929
|
*/
|
|
5640
|
-
|
|
5641
|
-
const
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5930
|
+
extractPredictionsFromText(response, expectedCount) {
|
|
5931
|
+
const predictions = [];
|
|
5932
|
+
const lines = response.split(/[\n\r]+/).filter(line => line.trim());
|
|
5933
|
+
for (const line of lines) {
|
|
5934
|
+
let cleaned = line.trim();
|
|
5935
|
+
// Skip empty lines and JSON brackets
|
|
5936
|
+
if (!cleaned || cleaned === '[' || cleaned === ']')
|
|
5937
|
+
continue;
|
|
5938
|
+
// Remove common prefixes like "1.", "- ", etc.
|
|
5939
|
+
if (/^\d+\./.test(cleaned)) {
|
|
5940
|
+
cleaned = cleaned.replace(/^\d+\.\s*/, '');
|
|
5941
|
+
}
|
|
5942
|
+
else if (cleaned.startsWith('- ')) {
|
|
5943
|
+
cleaned = cleaned.substring(2);
|
|
5944
|
+
}
|
|
5945
|
+
// Remove surrounding quotes
|
|
5946
|
+
if (cleaned.startsWith('"') && cleaned.endsWith('"')) {
|
|
5947
|
+
cleaned = cleaned.slice(1, -1);
|
|
5948
|
+
}
|
|
5949
|
+
// Remove trailing comma
|
|
5950
|
+
if (cleaned.endsWith(',')) {
|
|
5951
|
+
cleaned = cleaned.slice(0, -1).trim();
|
|
5952
|
+
}
|
|
5953
|
+
if (cleaned && predictions.length < expectedCount) {
|
|
5954
|
+
predictions.push(cleaned);
|
|
5955
|
+
}
|
|
5956
|
+
}
|
|
5957
|
+
return predictions;
|
|
5647
5958
|
}
|
|
5648
5959
|
/**
|
|
5649
|
-
*
|
|
5650
|
-
* Restores characterDesign, memories, and history.
|
|
5960
|
+
* Internal method to trigger prediction generation after NPC response
|
|
5651
5961
|
*/
|
|
5652
|
-
|
|
5962
|
+
async triggerReplyPrediction() {
|
|
5963
|
+
if (!this.generateReplyPrediction)
|
|
5964
|
+
return;
|
|
5965
|
+
// Fire and forget - don't block the main response
|
|
5966
|
+
this.generateReplyPredictions().catch(err => {
|
|
5967
|
+
this.logger.error('Background prediction generation failed:', err);
|
|
5968
|
+
});
|
|
5969
|
+
}
|
|
5970
|
+
// ===== Main API - Talk Methods =====
|
|
5971
|
+
/**
|
|
5972
|
+
* Talk to the NPC (non-streaming)
|
|
5973
|
+
*/
|
|
5974
|
+
async talk(message) {
|
|
5975
|
+
this._isTalking = true;
|
|
5653
5976
|
try {
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
this.
|
|
5657
|
-
//
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
this.
|
|
5673
|
-
|
|
5977
|
+
// Add user message to history
|
|
5978
|
+
const userMessage = { role: 'user', content: message };
|
|
5979
|
+
this.history.push(userMessage);
|
|
5980
|
+
// Build messages array with system prompt
|
|
5981
|
+
const messages = [
|
|
5982
|
+
{ role: 'system', content: this.buildSystemPrompt() },
|
|
5983
|
+
...this.history,
|
|
5984
|
+
];
|
|
5985
|
+
// Generate response
|
|
5986
|
+
const result = await this.chatClient.textGeneration({
|
|
5987
|
+
messages,
|
|
5988
|
+
temperature: this.temperature,
|
|
5989
|
+
});
|
|
5990
|
+
// Add assistant response to history
|
|
5991
|
+
const assistantMessage = { role: 'assistant', content: result.content };
|
|
5992
|
+
this.history.push(assistantMessage);
|
|
5993
|
+
// Trim history if needed
|
|
5994
|
+
this.trimHistory();
|
|
5995
|
+
this.emit('response', result.content);
|
|
5996
|
+
// Trigger reply prediction generation (fire and forget)
|
|
5997
|
+
this.triggerReplyPrediction();
|
|
5998
|
+
return result.content;
|
|
5674
5999
|
}
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
/**
|
|
5679
|
-
* Global AI Context Manager for managing NPC conversations and player context.
|
|
5680
|
-
*
|
|
5681
|
-
* Features:
|
|
5682
|
-
* - Player description management
|
|
5683
|
-
* - NPC conversation tracking
|
|
5684
|
-
* - Automatic conversation compaction (AutoCompact)
|
|
5685
|
-
*/
|
|
5686
|
-
/**
|
|
5687
|
-
* Global AI Context Manager
|
|
5688
|
-
* Manages NPC conversations and player context across the application
|
|
5689
|
-
*/
|
|
5690
|
-
class AIContextManager extends EventEmitter {
|
|
5691
|
-
constructor(config) {
|
|
5692
|
-
var _a, _b, _c, _d, _e;
|
|
5693
|
-
super();
|
|
5694
|
-
this.playerDescription = null;
|
|
5695
|
-
this.npcStates = new Map();
|
|
5696
|
-
this.autoCompactTimer = null;
|
|
5697
|
-
this.chatClientFactory = null;
|
|
5698
|
-
this.logger = Logger.getLogger('AIContextManager');
|
|
5699
|
-
this.config = {
|
|
5700
|
-
enableAutoCompact: (_a = config === null || config === void 0 ? void 0 : config.enableAutoCompact) !== null && _a !== void 0 ? _a : false,
|
|
5701
|
-
autoCompactMinMessages: (_b = config === null || config === void 0 ? void 0 : config.autoCompactMinMessages) !== null && _b !== void 0 ? _b : 20,
|
|
5702
|
-
autoCompactTimeoutSeconds: (_c = config === null || config === void 0 ? void 0 : config.autoCompactTimeoutSeconds) !== null && _c !== void 0 ? _c : 300,
|
|
5703
|
-
autoCompactCheckInterval: (_d = config === null || config === void 0 ? void 0 : config.autoCompactCheckInterval) !== null && _d !== void 0 ? _d : 60000,
|
|
5704
|
-
fastModel: (_e = config === null || config === void 0 ? void 0 : config.fastModel) !== null && _e !== void 0 ? _e : '',
|
|
5705
|
-
};
|
|
5706
|
-
// Start auto-compact check if enabled
|
|
5707
|
-
if (this.config.enableAutoCompact) {
|
|
5708
|
-
this.startAutoCompactCheck();
|
|
6000
|
+
finally {
|
|
6001
|
+
this._isTalking = false;
|
|
5709
6002
|
}
|
|
5710
6003
|
}
|
|
5711
|
-
// ===== Singleton Pattern =====
|
|
5712
6004
|
/**
|
|
5713
|
-
*
|
|
5714
|
-
* Creates a new instance if one doesn't exist
|
|
6005
|
+
* Talk to the NPC with streaming
|
|
5715
6006
|
*/
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
6007
|
+
async talkStream(message, onChunk, onComplete) {
|
|
6008
|
+
this._isTalking = true;
|
|
6009
|
+
try {
|
|
6010
|
+
// Add user message to history
|
|
6011
|
+
const userMessage = { role: 'user', content: message };
|
|
6012
|
+
this.history.push(userMessage);
|
|
6013
|
+
// Build messages array with system prompt
|
|
6014
|
+
const messages = [
|
|
6015
|
+
{ role: 'system', content: this.buildSystemPrompt() },
|
|
6016
|
+
...this.history,
|
|
6017
|
+
];
|
|
6018
|
+
// Generate response
|
|
6019
|
+
await this.chatClient.textGenerationStream({
|
|
6020
|
+
messages,
|
|
6021
|
+
temperature: this.temperature,
|
|
6022
|
+
onChunk,
|
|
6023
|
+
onComplete: (fullText) => {
|
|
6024
|
+
this._isTalking = false;
|
|
6025
|
+
// Add assistant response to history
|
|
6026
|
+
const assistantMessage = { role: 'assistant', content: fullText };
|
|
6027
|
+
this.history.push(assistantMessage);
|
|
6028
|
+
// Trim history if needed
|
|
6029
|
+
this.trimHistory();
|
|
6030
|
+
this.emit('response', fullText);
|
|
6031
|
+
// Trigger reply prediction generation (fire and forget)
|
|
6032
|
+
this.triggerReplyPrediction();
|
|
6033
|
+
if (onComplete) {
|
|
6034
|
+
onComplete(fullText);
|
|
6035
|
+
}
|
|
6036
|
+
},
|
|
6037
|
+
});
|
|
5719
6038
|
}
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
* Reset the singleton instance (useful for testing)
|
|
5724
|
-
*/
|
|
5725
|
-
static resetInstance() {
|
|
5726
|
-
if (AIContextManager._instance) {
|
|
5727
|
-
AIContextManager._instance.destroy();
|
|
5728
|
-
AIContextManager._instance = null;
|
|
6039
|
+
catch (error) {
|
|
6040
|
+
this._isTalking = false;
|
|
6041
|
+
throw error;
|
|
5729
6042
|
}
|
|
5730
6043
|
}
|
|
5731
|
-
// ===== Configuration =====
|
|
5732
6044
|
/**
|
|
5733
|
-
*
|
|
5734
|
-
*
|
|
6045
|
+
* Talk with structured output
|
|
6046
|
+
* @deprecated Use talkWithActions instead for NPC decision-making with actions
|
|
5735
6047
|
*/
|
|
5736
|
-
|
|
5737
|
-
this.
|
|
6048
|
+
async talkStructured(message, schemaName) {
|
|
6049
|
+
this.logger.warn('talkStructured is deprecated. Use talkWithActions instead for NPC decision-making with actions.');
|
|
6050
|
+
// Add user message to history
|
|
6051
|
+
const userMessage = { role: 'user', content: message };
|
|
6052
|
+
this.history.push(userMessage);
|
|
6053
|
+
// Generate structured response
|
|
6054
|
+
const result = await this.chatClient.generateStructured({
|
|
6055
|
+
schemaName,
|
|
6056
|
+
prompt: message,
|
|
6057
|
+
messages: [{ role: 'system', content: this.buildSystemPrompt() }, ...this.history],
|
|
6058
|
+
temperature: this.temperature,
|
|
6059
|
+
});
|
|
6060
|
+
// Add a text representation to history
|
|
6061
|
+
const assistantMessage = {
|
|
6062
|
+
role: 'assistant',
|
|
6063
|
+
content: JSON.stringify(result),
|
|
6064
|
+
};
|
|
6065
|
+
this.history.push(assistantMessage);
|
|
6066
|
+
this.trimHistory();
|
|
6067
|
+
return result;
|
|
5738
6068
|
}
|
|
5739
6069
|
/**
|
|
5740
|
-
*
|
|
6070
|
+
* Talk to the NPC with available actions (non-streaming)
|
|
6071
|
+
* @param message The message to send
|
|
6072
|
+
* @param actions List of actions the NPC can perform
|
|
6073
|
+
* @returns Response containing text and any action calls
|
|
5741
6074
|
*/
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
6075
|
+
async talkWithActions(message, actions) {
|
|
6076
|
+
this._isTalking = true;
|
|
6077
|
+
try {
|
|
6078
|
+
// Add user message to history
|
|
6079
|
+
const userMessage = { role: 'user', content: message };
|
|
6080
|
+
this.history.push(userMessage);
|
|
6081
|
+
// Convert NpcActions to ChatTools
|
|
6082
|
+
const tools = actions
|
|
6083
|
+
.filter(a => a && a.enabled !== false)
|
|
6084
|
+
.map(a => npcActionToTool(a));
|
|
6085
|
+
// Build messages array with system prompt
|
|
6086
|
+
const messages = [
|
|
6087
|
+
{ role: 'system', content: this.buildSystemPrompt() },
|
|
6088
|
+
...this.history,
|
|
6089
|
+
];
|
|
6090
|
+
// Generate response with tools
|
|
6091
|
+
const result = await this.chatClient.textGenerationWithTools({
|
|
6092
|
+
messages,
|
|
6093
|
+
temperature: this.temperature,
|
|
6094
|
+
tools,
|
|
6095
|
+
tool_choice: 'auto',
|
|
6096
|
+
});
|
|
6097
|
+
// Build response
|
|
6098
|
+
const response = {
|
|
6099
|
+
text: result.content || '',
|
|
6100
|
+
actionCalls: [],
|
|
6101
|
+
hasActions: false,
|
|
6102
|
+
};
|
|
6103
|
+
// Extract tool calls if any
|
|
6104
|
+
if (result.tool_calls) {
|
|
6105
|
+
response.actionCalls = result.tool_calls.map(tc => ({
|
|
6106
|
+
id: tc.id,
|
|
6107
|
+
actionName: tc.function.name,
|
|
6108
|
+
arguments: this.parseToolArguments(tc.function.arguments),
|
|
6109
|
+
}));
|
|
6110
|
+
response.hasActions = response.actionCalls.length > 0;
|
|
5749
6111
|
}
|
|
5750
|
-
|
|
5751
|
-
|
|
6112
|
+
// Add assistant response to history
|
|
6113
|
+
const assistantMessage = {
|
|
6114
|
+
role: 'assistant',
|
|
6115
|
+
content: response.text,
|
|
6116
|
+
tool_calls: result.tool_calls,
|
|
6117
|
+
};
|
|
6118
|
+
this.history.push(assistantMessage);
|
|
6119
|
+
this.trimHistory();
|
|
6120
|
+
this.emit('response', response.text);
|
|
6121
|
+
if (response.hasActions) {
|
|
6122
|
+
this.emit('actions', response.actionCalls);
|
|
5752
6123
|
}
|
|
6124
|
+
// Trigger reply prediction generation (fire and forget)
|
|
6125
|
+
this.triggerReplyPrediction();
|
|
6126
|
+
return response;
|
|
6127
|
+
}
|
|
6128
|
+
finally {
|
|
6129
|
+
this._isTalking = false;
|
|
5753
6130
|
}
|
|
5754
|
-
}
|
|
5755
|
-
// ===== Player Description =====
|
|
5756
|
-
/**
|
|
5757
|
-
* Set the player's description for AI context.
|
|
5758
|
-
* Used when generating reply predictions and for NPC context.
|
|
5759
|
-
* @param description Description of the player character
|
|
5760
|
-
*/
|
|
5761
|
-
setPlayerDescription(description) {
|
|
5762
|
-
this.playerDescription = description;
|
|
5763
|
-
this.emit('playerDescriptionChanged', description);
|
|
5764
|
-
}
|
|
5765
|
-
/**
|
|
5766
|
-
* Get the current player description.
|
|
5767
|
-
* @returns The player description, or null if not set
|
|
5768
|
-
*/
|
|
5769
|
-
getPlayerDescription() {
|
|
5770
|
-
return this.playerDescription;
|
|
5771
6131
|
}
|
|
5772
6132
|
/**
|
|
5773
|
-
*
|
|
6133
|
+
* Talk to the NPC with actions (streaming)
|
|
6134
|
+
* Text streams first, action calls are returned in onComplete
|
|
5774
6135
|
*/
|
|
5775
|
-
|
|
5776
|
-
this.
|
|
5777
|
-
|
|
6136
|
+
async talkWithActionsStream(message, actions, onChunk, onComplete) {
|
|
6137
|
+
this._isTalking = true;
|
|
6138
|
+
try {
|
|
6139
|
+
// Add user message to history
|
|
6140
|
+
const userMessage = { role: 'user', content: message };
|
|
6141
|
+
this.history.push(userMessage);
|
|
6142
|
+
// Convert NpcActions to ChatTools
|
|
6143
|
+
const tools = actions
|
|
6144
|
+
.filter(a => a && a.enabled !== false)
|
|
6145
|
+
.map(a => npcActionToTool(a));
|
|
6146
|
+
// Build messages array with system prompt
|
|
6147
|
+
const messages = [
|
|
6148
|
+
{ role: 'system', content: this.buildSystemPrompt() },
|
|
6149
|
+
...this.history,
|
|
6150
|
+
];
|
|
6151
|
+
// Generate response with tools (streaming)
|
|
6152
|
+
await this.chatClient.textGenerationWithToolsStream({
|
|
6153
|
+
messages,
|
|
6154
|
+
temperature: this.temperature,
|
|
6155
|
+
tools,
|
|
6156
|
+
tool_choice: 'auto',
|
|
6157
|
+
onChunk,
|
|
6158
|
+
onComplete: (result) => {
|
|
6159
|
+
this._isTalking = false;
|
|
6160
|
+
// Build response
|
|
6161
|
+
const response = {
|
|
6162
|
+
text: result.content || '',
|
|
6163
|
+
actionCalls: [],
|
|
6164
|
+
hasActions: false,
|
|
6165
|
+
};
|
|
6166
|
+
// Extract tool calls if any
|
|
6167
|
+
if (result.tool_calls) {
|
|
6168
|
+
response.actionCalls = result.tool_calls.map(tc => ({
|
|
6169
|
+
id: tc.id,
|
|
6170
|
+
actionName: tc.function.name,
|
|
6171
|
+
arguments: this.parseToolArguments(tc.function.arguments),
|
|
6172
|
+
}));
|
|
6173
|
+
response.hasActions = response.actionCalls.length > 0;
|
|
6174
|
+
}
|
|
6175
|
+
// Add assistant response to history
|
|
6176
|
+
const assistantMessage = {
|
|
6177
|
+
role: 'assistant',
|
|
6178
|
+
content: response.text,
|
|
6179
|
+
tool_calls: result.tool_calls,
|
|
6180
|
+
};
|
|
6181
|
+
this.history.push(assistantMessage);
|
|
6182
|
+
this.trimHistory();
|
|
6183
|
+
this.emit('response', response.text);
|
|
6184
|
+
if (response.hasActions) {
|
|
6185
|
+
this.emit('actions', response.actionCalls);
|
|
6186
|
+
}
|
|
6187
|
+
// Trigger reply prediction generation (fire and forget)
|
|
6188
|
+
this.triggerReplyPrediction();
|
|
6189
|
+
if (onComplete) {
|
|
6190
|
+
onComplete(response);
|
|
6191
|
+
}
|
|
6192
|
+
},
|
|
6193
|
+
});
|
|
6194
|
+
}
|
|
6195
|
+
catch (error) {
|
|
6196
|
+
this._isTalking = false;
|
|
6197
|
+
throw error;
|
|
6198
|
+
}
|
|
5778
6199
|
}
|
|
5779
|
-
// =====
|
|
6200
|
+
// ===== Action Results Reporting =====
|
|
5780
6201
|
/**
|
|
5781
|
-
*
|
|
5782
|
-
*
|
|
6202
|
+
* Report action results back to the conversation
|
|
6203
|
+
* Call this after executing actions to let the NPC know the results
|
|
5783
6204
|
*/
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
isCompacted: false,
|
|
5791
|
-
compactionCount: 0,
|
|
6205
|
+
reportActionResults(results) {
|
|
6206
|
+
for (const [callId, result] of Object.entries(results)) {
|
|
6207
|
+
this.history.push({
|
|
6208
|
+
role: 'tool',
|
|
6209
|
+
tool_call_id: callId,
|
|
6210
|
+
content: result,
|
|
5792
6211
|
});
|
|
5793
6212
|
}
|
|
5794
6213
|
}
|
|
5795
6214
|
/**
|
|
5796
|
-
*
|
|
5797
|
-
* @param npc The NPC client to unregister
|
|
6215
|
+
* Report a single action result
|
|
5798
6216
|
*/
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
6217
|
+
reportActionResult(callId, result) {
|
|
6218
|
+
this.history.push({
|
|
6219
|
+
role: 'tool',
|
|
6220
|
+
tool_call_id: callId,
|
|
6221
|
+
content: result,
|
|
6222
|
+
});
|
|
5803
6223
|
}
|
|
5804
6224
|
/**
|
|
5805
|
-
*
|
|
5806
|
-
* Called after each Talk() exchange.
|
|
5807
|
-
* @param npc The NPC client that had a conversation
|
|
6225
|
+
* Parse tool arguments from JSON string
|
|
5808
6226
|
*/
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
return;
|
|
5812
|
-
|
|
5813
|
-
|
|
6227
|
+
parseToolArguments(args) {
|
|
6228
|
+
try {
|
|
6229
|
+
return JSON.parse(args);
|
|
6230
|
+
}
|
|
6231
|
+
catch (_a) {
|
|
6232
|
+
return {};
|
|
5814
6233
|
}
|
|
5815
|
-
const state = this.npcStates.get(npc);
|
|
5816
|
-
state.lastConversationTime = new Date();
|
|
5817
|
-
state.isCompacted = false; // Reset compaction flag on new conversation
|
|
5818
6234
|
}
|
|
6235
|
+
// ===== Conversation History Management =====
|
|
5819
6236
|
/**
|
|
5820
|
-
* Get
|
|
6237
|
+
* Get conversation history
|
|
5821
6238
|
*/
|
|
5822
|
-
|
|
5823
|
-
return
|
|
6239
|
+
getHistory() {
|
|
6240
|
+
return [...this.history];
|
|
5824
6241
|
}
|
|
5825
6242
|
/**
|
|
5826
|
-
* Get the
|
|
6243
|
+
* Get the number of messages in history
|
|
5827
6244
|
*/
|
|
5828
|
-
|
|
5829
|
-
return this.
|
|
6245
|
+
getHistoryLength() {
|
|
6246
|
+
return this.history.length;
|
|
5830
6247
|
}
|
|
5831
|
-
// ===== Auto Compaction =====
|
|
5832
6248
|
/**
|
|
5833
|
-
*
|
|
5834
|
-
*
|
|
5835
|
-
* @returns True if eligible for compaction
|
|
6249
|
+
* Clear conversation history.
|
|
6250
|
+
* The character design and memories will be preserved.
|
|
5836
6251
|
*/
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
const state = this.npcStates.get(npc);
|
|
5841
|
-
if (!state)
|
|
5842
|
-
return false;
|
|
5843
|
-
// Check if already compacted since last conversation
|
|
5844
|
-
if (state.isCompacted)
|
|
5845
|
-
return false;
|
|
5846
|
-
// Check message count
|
|
5847
|
-
const history = npc.getHistory();
|
|
5848
|
-
const nonSystemMessages = history.filter(m => m.role !== 'system').length;
|
|
5849
|
-
if (nonSystemMessages < this.config.autoCompactMinMessages)
|
|
5850
|
-
return false;
|
|
5851
|
-
// Check time since last conversation
|
|
5852
|
-
const timeSinceLastConversation = (Date.now() - state.lastConversationTime.getTime()) / 1000;
|
|
5853
|
-
if (timeSinceLastConversation < this.config.autoCompactTimeoutSeconds)
|
|
5854
|
-
return false;
|
|
5855
|
-
return true;
|
|
6252
|
+
clearHistory() {
|
|
6253
|
+
this.history = [];
|
|
6254
|
+
this.emit('history_cleared');
|
|
5856
6255
|
}
|
|
5857
6256
|
/**
|
|
5858
|
-
*
|
|
5859
|
-
*
|
|
5860
|
-
* @param npc The NPC to compact
|
|
5861
|
-
* @returns True if compaction succeeded
|
|
6257
|
+
* Revert the last exchange (user message and assistant response) from history.
|
|
6258
|
+
* @returns true if reverted, false if not enough history
|
|
5862
6259
|
*/
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
this.logger.error('Cannot compact: No chat client factory set. Call setChatClientFactory() first.');
|
|
5870
|
-
return false;
|
|
5871
|
-
}
|
|
5872
|
-
const history = npc.getHistory();
|
|
5873
|
-
const nonSystemMessages = history.filter(m => m.role !== 'system');
|
|
5874
|
-
if (nonSystemMessages.length < 2) {
|
|
5875
|
-
this.logger.info('Skipping compaction: not enough messages');
|
|
5876
|
-
return false;
|
|
5877
|
-
}
|
|
5878
|
-
try {
|
|
5879
|
-
this.logger.info(`Starting compaction (${nonSystemMessages.length} messages)`);
|
|
5880
|
-
// Build conversation text for summarization
|
|
5881
|
-
const conversationText = nonSystemMessages
|
|
5882
|
-
.map(m => `${m.role}: ${m.content}`)
|
|
5883
|
-
.join('\n');
|
|
5884
|
-
// Create summarization prompt
|
|
5885
|
-
const summaryPrompt = `Summarize the following conversation concisely. Focus on:
|
|
5886
|
-
1. Key topics discussed
|
|
5887
|
-
2. Important information exchanged
|
|
5888
|
-
3. Any decisions or commitments made
|
|
5889
|
-
4. The emotional tone
|
|
5890
|
-
|
|
5891
|
-
Keep the summary under 200 words. Write in third person.
|
|
5892
|
-
|
|
5893
|
-
Conversation:
|
|
5894
|
-
${conversationText}`;
|
|
5895
|
-
// Use chat client for summarization
|
|
5896
|
-
const chatClient = this.chatClientFactory();
|
|
5897
|
-
const result = await chatClient.textGeneration({
|
|
5898
|
-
messages: [{ role: 'user', content: summaryPrompt }],
|
|
5899
|
-
temperature: 0.5,
|
|
5900
|
-
model: this.config.fastModel || undefined,
|
|
5901
|
-
});
|
|
5902
|
-
if (!result.content) {
|
|
5903
|
-
const error = 'Empty response from summarization';
|
|
5904
|
-
this.logger.error(`Compaction failed: ${error}`);
|
|
5905
|
-
this.emit('compactionFailed', npc, error);
|
|
5906
|
-
return false;
|
|
6260
|
+
revertHistory() {
|
|
6261
|
+
let lastAssistantIndex = -1;
|
|
6262
|
+
let lastUserIndex = -1;
|
|
6263
|
+
for (let i = this.history.length - 1; i >= 0; i--) {
|
|
6264
|
+
if (this.history[i].role === 'assistant' && lastAssistantIndex === -1) {
|
|
6265
|
+
lastAssistantIndex = i;
|
|
5907
6266
|
}
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
// Update state
|
|
5912
|
-
const state = this.npcStates.get(npc);
|
|
5913
|
-
if (state) {
|
|
5914
|
-
state.isCompacted = true;
|
|
5915
|
-
state.compactionCount++;
|
|
6267
|
+
else if (this.history[i].role === 'user' && lastAssistantIndex !== -1 && lastUserIndex === -1) {
|
|
6268
|
+
lastUserIndex = i;
|
|
6269
|
+
break;
|
|
5916
6270
|
}
|
|
5917
|
-
this.logger.info(`Compaction completed. Summary: ${result.content.substring(0, 100)}...`);
|
|
5918
|
-
this.emit('npcCompacted', npc);
|
|
5919
|
-
return true;
|
|
5920
6271
|
}
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
this.
|
|
5924
|
-
this.
|
|
5925
|
-
|
|
6272
|
+
if (lastAssistantIndex !== -1 && lastUserIndex !== -1) {
|
|
6273
|
+
// Remove in reverse order to maintain indices
|
|
6274
|
+
this.history.splice(lastAssistantIndex, 1);
|
|
6275
|
+
this.history.splice(lastUserIndex, 1);
|
|
6276
|
+
this.emit('history_reverted');
|
|
6277
|
+
return true;
|
|
5926
6278
|
}
|
|
6279
|
+
return false;
|
|
5927
6280
|
}
|
|
5928
6281
|
/**
|
|
5929
|
-
*
|
|
5930
|
-
* @
|
|
6282
|
+
* Revert (remove) the last N chat messages from history
|
|
6283
|
+
* @param count Number of messages to remove
|
|
6284
|
+
* @returns Number of messages actually removed
|
|
5931
6285
|
*/
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
if (eligibleNpcs.length === 0) {
|
|
6286
|
+
revertChatMessages(count) {
|
|
6287
|
+
if (count <= 0)
|
|
5935
6288
|
return 0;
|
|
6289
|
+
const messagesToRemove = Math.min(count, this.history.length);
|
|
6290
|
+
const originalCount = this.history.length;
|
|
6291
|
+
this.history = this.history.slice(0, -messagesToRemove);
|
|
6292
|
+
const actuallyRemoved = originalCount - this.history.length;
|
|
6293
|
+
if (actuallyRemoved > 0) {
|
|
6294
|
+
this.emit('history_reverted', actuallyRemoved);
|
|
5936
6295
|
}
|
|
5937
|
-
|
|
5938
|
-
let successCount = 0;
|
|
5939
|
-
for (const npc of eligibleNpcs) {
|
|
5940
|
-
const success = await this.compactConversation(npc);
|
|
5941
|
-
if (success)
|
|
5942
|
-
successCount++;
|
|
5943
|
-
}
|
|
5944
|
-
return successCount;
|
|
6296
|
+
return actuallyRemoved;
|
|
5945
6297
|
}
|
|
5946
|
-
// ===== Auto Compact Timer =====
|
|
5947
6298
|
/**
|
|
5948
|
-
*
|
|
6299
|
+
* Revert to a specific point in history
|
|
6300
|
+
* @deprecated Use revertHistory() or revertChatMessages() instead
|
|
5949
6301
|
*/
|
|
5950
|
-
|
|
5951
|
-
if (this.
|
|
5952
|
-
this.
|
|
6302
|
+
revertToMessage(index) {
|
|
6303
|
+
if (index >= 0 && index < this.history.length) {
|
|
6304
|
+
this.history = this.history.slice(0, index + 1);
|
|
6305
|
+
this.emit('history_reverted', index);
|
|
5953
6306
|
}
|
|
5954
|
-
this.autoCompactTimer = setInterval(() => {
|
|
5955
|
-
this.runAutoCompactCheck();
|
|
5956
|
-
}, this.config.autoCompactCheckInterval);
|
|
5957
6307
|
}
|
|
5958
6308
|
/**
|
|
5959
|
-
*
|
|
6309
|
+
* Append a message to history manually
|
|
5960
6310
|
*/
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
this.autoCompactTimer = null;
|
|
5965
|
-
}
|
|
6311
|
+
appendMessage(message) {
|
|
6312
|
+
this.history.push(message);
|
|
6313
|
+
this.trimHistory();
|
|
5966
6314
|
}
|
|
5967
6315
|
/**
|
|
5968
|
-
*
|
|
6316
|
+
* Alias for appendMessage (Unity SDK compatibility)
|
|
5969
6317
|
*/
|
|
5970
|
-
|
|
5971
|
-
if (!
|
|
6318
|
+
appendChatMessage(role, content) {
|
|
6319
|
+
if (!role || !content) {
|
|
6320
|
+
this.logger.warn('Role and content cannot be empty');
|
|
5972
6321
|
return;
|
|
5973
|
-
const eligibleNpcs = Array.from(this.npcStates.keys()).filter(npc => this.isEligibleForCompaction(npc));
|
|
5974
|
-
for (const npc of eligibleNpcs) {
|
|
5975
|
-
// Fire and forget - don't block
|
|
5976
|
-
this.compactConversation(npc).catch(err => {
|
|
5977
|
-
this.logger.error('Auto-compact error:', err);
|
|
5978
|
-
});
|
|
5979
6322
|
}
|
|
6323
|
+
this.appendMessage({ role: role, content });
|
|
5980
6324
|
}
|
|
5981
|
-
// ===== Lifecycle =====
|
|
5982
6325
|
/**
|
|
5983
|
-
*
|
|
6326
|
+
* Trim history to max length
|
|
5984
6327
|
*/
|
|
5985
|
-
|
|
5986
|
-
this.
|
|
5987
|
-
|
|
6328
|
+
trimHistory() {
|
|
6329
|
+
if (this.history.length > this.maxHistoryLength) {
|
|
6330
|
+
// Keep the most recent messages
|
|
6331
|
+
this.history = this.history.slice(-this.maxHistoryLength);
|
|
6332
|
+
}
|
|
5988
6333
|
}
|
|
6334
|
+
// ===== Save/Load =====
|
|
5989
6335
|
/**
|
|
5990
|
-
*
|
|
6336
|
+
* Save the current conversation history to a serializable format.
|
|
6337
|
+
* Includes characterDesign, memories, and history.
|
|
5991
6338
|
*/
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
6339
|
+
saveHistory() {
|
|
6340
|
+
const saveData = {
|
|
6341
|
+
characterDesign: this.characterDesign,
|
|
6342
|
+
memories: Array.from(this.memories.entries()).map(([name, content]) => ({ name, content })),
|
|
6343
|
+
history: this.history,
|
|
6344
|
+
};
|
|
6345
|
+
return JSON.stringify(saveData);
|
|
5995
6346
|
}
|
|
5996
6347
|
/**
|
|
5997
|
-
*
|
|
6348
|
+
* Load conversation history from serialized data.
|
|
6349
|
+
* Restores characterDesign, memories, and history.
|
|
5998
6350
|
*/
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
6351
|
+
loadHistory(saveData) {
|
|
6352
|
+
try {
|
|
6353
|
+
const data = JSON.parse(saveData);
|
|
6354
|
+
// Load character design (with backwards compatibility for old systemPrompt field)
|
|
6355
|
+
this.characterDesign = data.characterDesign || data.systemPrompt || this.characterDesign;
|
|
6356
|
+
// Load memories
|
|
6357
|
+
this.memories.clear();
|
|
6358
|
+
if (data.memories && Array.isArray(data.memories)) {
|
|
6359
|
+
for (const memory of data.memories) {
|
|
6360
|
+
if (memory.name && memory.content) {
|
|
6361
|
+
this.memories.set(memory.name, memory.content);
|
|
6362
|
+
}
|
|
6363
|
+
}
|
|
6364
|
+
}
|
|
6365
|
+
// Load history (skip system messages as they'll be rebuilt from characterDesign + memories)
|
|
6366
|
+
this.history = (data.history || []).filter(m => m.role !== 'system');
|
|
6367
|
+
this.emit('history_loaded');
|
|
6368
|
+
return true;
|
|
6369
|
+
}
|
|
6370
|
+
catch (error) {
|
|
6371
|
+
this.logger.error('Failed to load history:', error);
|
|
6372
|
+
return false;
|
|
6373
|
+
}
|
|
6004
6374
|
}
|
|
6005
6375
|
}
|
|
6006
|
-
AIContextManager._instance = null;
|
|
6007
|
-
/**
|
|
6008
|
-
* Default AIContextManager instance
|
|
6009
|
-
* Can be used as a global context manager
|
|
6010
|
-
*/
|
|
6011
|
-
const defaultContextManager = AIContextManager.getInstance();
|
|
6012
6376
|
|
|
6013
6377
|
/**
|
|
6014
6378
|
* Schema Library for managing JSON schemas for AI structured output generation
|
|
@@ -6220,10 +6584,12 @@ class PlayKitSDK extends EventEmitter {
|
|
|
6220
6584
|
this.chatProvider = new ChatProvider(this.authManager, this.config);
|
|
6221
6585
|
this.imageProvider = new ImageProvider(this.authManager, this.config);
|
|
6222
6586
|
this.transcriptionProvider = new TranscriptionProvider(this.authManager, this.config);
|
|
6587
|
+
this.ttsProvider = new TTSProvider(this.authManager, this.config);
|
|
6223
6588
|
// Connect providers to player client for balance checking
|
|
6224
6589
|
this.chatProvider.setPlayerClient(this.playerClient);
|
|
6225
6590
|
this.imageProvider.setPlayerClient(this.playerClient);
|
|
6226
6591
|
this.transcriptionProvider.setPlayerClient(this.playerClient);
|
|
6592
|
+
this.ttsProvider.setPlayerClient(this.playerClient);
|
|
6227
6593
|
// Initialize AI context manager
|
|
6228
6594
|
this.contextManager = new AIContextManager(this.config.aiContext);
|
|
6229
6595
|
// Set chat client factory for compaction
|
|
@@ -6367,20 +6733,20 @@ class PlayKitSDK extends EventEmitter {
|
|
|
6367
6733
|
// Create indicator element
|
|
6368
6734
|
this.devTokenIndicator = document.createElement('div');
|
|
6369
6735
|
this.devTokenIndicator.textContent = 'DeveloperToken';
|
|
6370
|
-
this.devTokenIndicator.style.cssText = `
|
|
6371
|
-
position: fixed;
|
|
6372
|
-
top: 10px;
|
|
6373
|
-
left: 10px;
|
|
6374
|
-
background-color: #dc2626;
|
|
6375
|
-
color: white;
|
|
6376
|
-
padding: 4px 12px;
|
|
6377
|
-
border-radius: 4px;
|
|
6378
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
6379
|
-
font-size: 12px;
|
|
6380
|
-
font-weight: 600;
|
|
6381
|
-
z-index: 999999;
|
|
6382
|
-
pointer-events: none;
|
|
6383
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
6736
|
+
this.devTokenIndicator.style.cssText = `
|
|
6737
|
+
position: fixed;
|
|
6738
|
+
top: 10px;
|
|
6739
|
+
left: 10px;
|
|
6740
|
+
background-color: #dc2626;
|
|
6741
|
+
color: white;
|
|
6742
|
+
padding: 4px 12px;
|
|
6743
|
+
border-radius: 4px;
|
|
6744
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
6745
|
+
font-size: 12px;
|
|
6746
|
+
font-weight: 600;
|
|
6747
|
+
z-index: 999999;
|
|
6748
|
+
pointer-events: none;
|
|
6749
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
6384
6750
|
`;
|
|
6385
6751
|
document.body.appendChild(this.devTokenIndicator);
|
|
6386
6752
|
}
|
|
@@ -6472,6 +6838,14 @@ class PlayKitSDK extends EventEmitter {
|
|
|
6472
6838
|
this.ensureInitialized();
|
|
6473
6839
|
return new TranscriptionClient(this.transcriptionProvider, model || this.config.defaultTranscriptionModel);
|
|
6474
6840
|
}
|
|
6841
|
+
/**
|
|
6842
|
+
* Create a TTS client for text-to-speech
|
|
6843
|
+
* @param model - TTS model to use (default: 'default-tts-model')
|
|
6844
|
+
*/
|
|
6845
|
+
createTTSClient(model) {
|
|
6846
|
+
this.ensureInitialized();
|
|
6847
|
+
return new TTSClient(this.ttsProvider, model || this.config.defaultTTSModel);
|
|
6848
|
+
}
|
|
6475
6849
|
/**
|
|
6476
6850
|
* Create an NPC client
|
|
6477
6851
|
* Automatically registers with AIContextManager
|
|
@@ -6769,9 +7143,7 @@ class TokenValidator {
|
|
|
6769
7143
|
*/
|
|
6770
7144
|
async validateToken(token, gameId) {
|
|
6771
7145
|
var _a, _b;
|
|
6772
|
-
const headers = {
|
|
6773
|
-
'Authorization': `Bearer ${token}`,
|
|
6774
|
-
};
|
|
7146
|
+
const headers = Object.assign({ 'Authorization': `Bearer ${token}` }, getSDKHeaders());
|
|
6775
7147
|
if (gameId) {
|
|
6776
7148
|
headers['X-Game-Id'] = gameId;
|
|
6777
7149
|
}
|
|
@@ -6801,9 +7173,7 @@ class TokenValidator {
|
|
|
6801
7173
|
*/
|
|
6802
7174
|
async verifyToken(token, gameId) {
|
|
6803
7175
|
var _a, _b;
|
|
6804
|
-
const headers = {
|
|
6805
|
-
'Authorization': `Bearer ${token}`,
|
|
6806
|
-
};
|
|
7176
|
+
const headers = Object.assign({ 'Authorization': `Bearer ${token}` }, getSDKHeaders());
|
|
6807
7177
|
if (gameId) {
|
|
6808
7178
|
headers['X-Game-Id'] = gameId;
|
|
6809
7179
|
}
|
|
@@ -6883,6 +7253,7 @@ exports.PlayerClient = PlayerClient;
|
|
|
6883
7253
|
exports.RechargeManager = RechargeManager;
|
|
6884
7254
|
exports.SchemaLibrary = SchemaLibrary;
|
|
6885
7255
|
exports.StreamParser = StreamParser;
|
|
7256
|
+
exports.TTSClient = TTSClient;
|
|
6886
7257
|
exports.TokenStorage = TokenStorage;
|
|
6887
7258
|
exports.TokenValidator = TokenValidator;
|
|
6888
7259
|
exports.TranscriptionClient = TranscriptionClient;
|