@myned-ai/avatar-chat-widget 0.0.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +150 -170
- package/dist/asset/nyx.zip +0 -0
- package/dist/avatar-chat-widget.d.ts +18 -90
- package/dist/avatar-chat-widget.js +715 -333
- package/dist/avatar-chat-widget.js.map +1 -1
- package/dist/avatar-chat-widget.umd.js +1 -1
- package/dist/avatar-chat-widget.umd.js.map +1 -1
- package/dist/pcm16-processor.worklet.js +126 -0
- package/package.json +2 -1
- package/public/asset/nyx.zip +0 -0
- package/public/pcm16-processor.worklet.js +126 -0
|
@@ -3,7 +3,7 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
|
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
4
|
var _a;
|
|
5
5
|
const __vite_import_meta_env__ = {};
|
|
6
|
-
const DEFAULT_CONFIG$
|
|
6
|
+
const DEFAULT_CONFIG$2 = {
|
|
7
7
|
auth: {
|
|
8
8
|
enabled: false
|
|
9
9
|
// Dev mode: auth disabled for local testing
|
|
@@ -61,14 +61,39 @@ const DEFAULT_CONFIG$1 = {
|
|
|
61
61
|
ui: {
|
|
62
62
|
avatarBackgroundColor: "0xffffff",
|
|
63
63
|
useIrisOcclusion: true
|
|
64
|
+
},
|
|
65
|
+
assets: {
|
|
66
|
+
// Default to local paths (works in dev mode)
|
|
67
|
+
// CDN usage will auto-detect and override this in widget.ts init()
|
|
68
|
+
baseUrl: "",
|
|
69
|
+
// Empty = use root path (works with Vite's public folder)
|
|
70
|
+
defaultAvatarPath: "/asset/nyx.zip"
|
|
64
71
|
}
|
|
65
72
|
};
|
|
66
|
-
|
|
73
|
+
function deepClone(obj) {
|
|
74
|
+
if (obj === null || typeof obj !== "object") {
|
|
75
|
+
return obj;
|
|
76
|
+
}
|
|
77
|
+
if (obj instanceof Date) {
|
|
78
|
+
return new Date(obj.getTime());
|
|
79
|
+
}
|
|
80
|
+
if (Array.isArray(obj)) {
|
|
81
|
+
return obj.map((item) => deepClone(item));
|
|
82
|
+
}
|
|
83
|
+
const clonedObj = {};
|
|
84
|
+
for (const key in obj) {
|
|
85
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
86
|
+
clonedObj[key] = deepClone(obj[key]);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return clonedObj;
|
|
90
|
+
}
|
|
91
|
+
let runtimeConfig = deepClone(DEFAULT_CONFIG$2);
|
|
67
92
|
function deepMerge(target, source) {
|
|
68
93
|
const result = { ...target };
|
|
69
94
|
for (const key in source) {
|
|
70
95
|
if (source[key] !== void 0) {
|
|
71
|
-
if (typeof source[key] === "object" && source[key] !== null && !Array.isArray(source[key]) && typeof target[key] === "object") {
|
|
96
|
+
if (typeof source[key] === "object" && source[key] !== null && !Array.isArray(source[key]) && typeof target[key] === "object" && typeof source[key] !== "function") {
|
|
72
97
|
result[key] = deepMerge(target[key], source[key]);
|
|
73
98
|
} else {
|
|
74
99
|
result[key] = source[key];
|
|
@@ -80,9 +105,21 @@ function deepMerge(target, source) {
|
|
|
80
105
|
function setConfig(config) {
|
|
81
106
|
runtimeConfig = deepMerge(runtimeConfig, config);
|
|
82
107
|
}
|
|
83
|
-
const CONFIG = new Proxy(
|
|
84
|
-
get(
|
|
85
|
-
|
|
108
|
+
const CONFIG = new Proxy(runtimeConfig, {
|
|
109
|
+
get(target, prop) {
|
|
110
|
+
if (typeof prop === "string" && prop in target) {
|
|
111
|
+
return target[prop];
|
|
112
|
+
}
|
|
113
|
+
if (typeof prop === "symbol") {
|
|
114
|
+
return target[prop];
|
|
115
|
+
}
|
|
116
|
+
throw new Error(`Invalid config property: ${String(prop)}`);
|
|
117
|
+
},
|
|
118
|
+
set(_target2, prop) {
|
|
119
|
+
throw new Error(`CONFIG is read-only. Use setConfig() to update. Attempted to set: ${String(prop)}`);
|
|
120
|
+
},
|
|
121
|
+
deleteProperty(_target2, prop) {
|
|
122
|
+
throw new Error(`CONFIG is read-only. Cannot delete property: ${String(prop)}`);
|
|
86
123
|
}
|
|
87
124
|
});
|
|
88
125
|
const LogLevel = {
|
|
@@ -258,7 +295,9 @@ class LazyAvatar {
|
|
|
258
295
|
const { GaussianAvatar: GaussianAvatar2 } = await Promise.resolve().then(() => GaussianAvatar$1);
|
|
259
296
|
this._removePlaceholder();
|
|
260
297
|
this._avatar = new GaussianAvatar2(this._container, this._assetsPath);
|
|
261
|
-
|
|
298
|
+
if ("start" in this._avatar && typeof this._avatar.start === "function") {
|
|
299
|
+
await this._avatar.start();
|
|
300
|
+
}
|
|
262
301
|
if (this._pendingState !== "Idle") {
|
|
263
302
|
this._avatar.setChatState(this._pendingState);
|
|
264
303
|
}
|
|
@@ -284,7 +323,9 @@ class LazyAvatar {
|
|
|
284
323
|
*/
|
|
285
324
|
start() {
|
|
286
325
|
if (this._avatar) {
|
|
287
|
-
this._avatar.start
|
|
326
|
+
if ("start" in this._avatar && typeof this._avatar.start === "function") {
|
|
327
|
+
this._avatar.start();
|
|
328
|
+
}
|
|
288
329
|
} else {
|
|
289
330
|
this.load();
|
|
290
331
|
}
|
|
@@ -323,12 +364,12 @@ class LazyAvatar {
|
|
|
323
364
|
}
|
|
324
365
|
}
|
|
325
366
|
pause() {
|
|
326
|
-
if (this._avatar && "pause" in this._avatar) {
|
|
367
|
+
if (this._avatar && "pause" in this._avatar && typeof this._avatar.pause === "function") {
|
|
327
368
|
this._avatar.pause();
|
|
328
369
|
}
|
|
329
370
|
}
|
|
330
371
|
resume() {
|
|
331
|
-
if (this._avatar && "resume" in this._avatar) {
|
|
372
|
+
if (this._avatar && "resume" in this._avatar && typeof this._avatar.resume === "function") {
|
|
332
373
|
this._avatar.resume();
|
|
333
374
|
}
|
|
334
375
|
}
|
|
@@ -391,20 +432,45 @@ class EventEmitter {
|
|
|
391
432
|
class ErrorBoundary {
|
|
392
433
|
constructor() {
|
|
393
434
|
this.errorHandlers = /* @__PURE__ */ new Map();
|
|
394
|
-
this.
|
|
395
|
-
this.
|
|
435
|
+
this.errorWindows = /* @__PURE__ */ new Map();
|
|
436
|
+
this.maxErrorsPerWindow = 10;
|
|
437
|
+
this.errorWindowMs = 6e4;
|
|
438
|
+
this.circuitResetMs = 3e4;
|
|
396
439
|
}
|
|
440
|
+
// 30 seconds to try recovery
|
|
397
441
|
registerHandler(context, handler) {
|
|
398
442
|
this.errorHandlers.set(context, handler);
|
|
399
443
|
}
|
|
400
444
|
handleError(error2, context) {
|
|
401
445
|
console.error(`[${context}]`, error2);
|
|
402
|
-
const
|
|
403
|
-
this.
|
|
404
|
-
if (
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
446
|
+
const now = Date.now();
|
|
447
|
+
const errorWindow = this.errorWindows.get(context);
|
|
448
|
+
if (errorWindow?.circuitOpen) {
|
|
449
|
+
if (errorWindow.circuitOpenedAt && now - errorWindow.circuitOpenedAt > this.circuitResetMs) {
|
|
450
|
+
console.info(`[${context}] Circuit breaker reset attempt - clearing error history`);
|
|
451
|
+
this.errorWindows.delete(context);
|
|
452
|
+
} else {
|
|
453
|
+
console.warn(`[${context}] Circuit breaker open - error suppressed`);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
if (!errorWindow || now - errorWindow.firstError > this.errorWindowMs) {
|
|
458
|
+
this.errorWindows.set(context, {
|
|
459
|
+
count: 1,
|
|
460
|
+
firstError: now,
|
|
461
|
+
circuitOpen: false
|
|
462
|
+
});
|
|
463
|
+
} else {
|
|
464
|
+
errorWindow.count++;
|
|
465
|
+
if (errorWindow.count > this.maxErrorsPerWindow) {
|
|
466
|
+
errorWindow.circuitOpen = true;
|
|
467
|
+
errorWindow.circuitOpenedAt = now;
|
|
468
|
+
console.error(
|
|
469
|
+
`[${context}] Too many errors (${errorWindow.count} in ${this.errorWindowMs}ms). Circuit breaker triggered for ${this.circuitResetMs}ms.`
|
|
470
|
+
);
|
|
471
|
+
this.notifyUser(`Service temporarily unavailable: ${context}. Retrying in ${this.circuitResetMs / 1e3}s...`);
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
408
474
|
}
|
|
409
475
|
const handler = this.errorHandlers.get(context);
|
|
410
476
|
if (handler) {
|
|
@@ -432,15 +498,33 @@ class ErrorBoundary {
|
|
|
432
498
|
});
|
|
433
499
|
window.dispatchEvent(event);
|
|
434
500
|
}
|
|
501
|
+
/**
|
|
502
|
+
* Reset error tracking for a context (or all contexts)
|
|
503
|
+
*/
|
|
435
504
|
reset(context) {
|
|
436
505
|
if (context) {
|
|
437
|
-
this.
|
|
506
|
+
this.errorWindows.delete(context);
|
|
438
507
|
} else {
|
|
439
|
-
this.
|
|
508
|
+
this.errorWindows.clear();
|
|
440
509
|
}
|
|
441
510
|
}
|
|
511
|
+
/**
|
|
512
|
+
* Get current error count for a context
|
|
513
|
+
*/
|
|
442
514
|
getErrorCount(context) {
|
|
443
|
-
return this.
|
|
515
|
+
return this.errorWindows.get(context)?.count || 0;
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Check if circuit breaker is open for a context
|
|
519
|
+
*/
|
|
520
|
+
isCircuitOpen(context) {
|
|
521
|
+
return this.errorWindows.get(context)?.circuitOpen || false;
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Get error window stats for monitoring/debugging
|
|
525
|
+
*/
|
|
526
|
+
getStats(context) {
|
|
527
|
+
return this.errorWindows.get(context) || null;
|
|
444
528
|
}
|
|
445
529
|
}
|
|
446
530
|
const errorBoundary = new ErrorBoundary();
|
|
@@ -584,9 +668,11 @@ class BinaryProtocol {
|
|
|
584
668
|
const headerSize = 5;
|
|
585
669
|
const audioData = buffer.slice(headerSize);
|
|
586
670
|
return {
|
|
587
|
-
type,
|
|
588
|
-
|
|
589
|
-
timestamp
|
|
671
|
+
type: "audio_chunk",
|
|
672
|
+
data: audioData,
|
|
673
|
+
timestamp,
|
|
674
|
+
sessionId: ""
|
|
675
|
+
// SessionId not included in binary protocol, set by handler
|
|
590
676
|
};
|
|
591
677
|
}
|
|
592
678
|
/**
|
|
@@ -598,10 +684,16 @@ class BinaryProtocol {
|
|
|
598
684
|
const weightsData = new Float32Array(buffer, headerSize, 52);
|
|
599
685
|
const weights = new Float32Array(52);
|
|
600
686
|
weights.set(weightsData);
|
|
687
|
+
const weightsRecord = {};
|
|
688
|
+
weights.forEach((value, index) => {
|
|
689
|
+
weightsRecord[`blend_${index}`] = value;
|
|
690
|
+
});
|
|
601
691
|
return {
|
|
602
|
-
type,
|
|
603
|
-
weights:
|
|
604
|
-
timestamp
|
|
692
|
+
type: "blendshape",
|
|
693
|
+
weights: weightsRecord,
|
|
694
|
+
timestamp,
|
|
695
|
+
sessionId: ""
|
|
696
|
+
// SessionId not included in binary protocol, set by handler
|
|
605
697
|
};
|
|
606
698
|
}
|
|
607
699
|
/**
|
|
@@ -618,11 +710,22 @@ class BinaryProtocol {
|
|
|
618
710
|
const weightsData = new Float32Array(buffer, weightsStart, 52);
|
|
619
711
|
const weights = new Float32Array(52);
|
|
620
712
|
weights.set(weightsData);
|
|
713
|
+
const weightsRecord = {};
|
|
714
|
+
weights.forEach((value, index) => {
|
|
715
|
+
weightsRecord[`blend_${index}`] = value;
|
|
716
|
+
});
|
|
717
|
+
const bytes = new Uint8Array(audioData);
|
|
718
|
+
const binary = String.fromCharCode.apply(null, Array.from(bytes));
|
|
719
|
+
const base64Audio = btoa(binary);
|
|
621
720
|
return {
|
|
622
|
-
type,
|
|
623
|
-
audio:
|
|
624
|
-
weights:
|
|
625
|
-
timestamp
|
|
721
|
+
type: "sync_frame",
|
|
722
|
+
audio: base64Audio,
|
|
723
|
+
weights: weightsRecord,
|
|
724
|
+
timestamp,
|
|
725
|
+
sessionId: "",
|
|
726
|
+
// SessionId not included in binary protocol, set by handler
|
|
727
|
+
frameIndex: 0
|
|
728
|
+
// FrameIndex not included in binary protocol, set by handler
|
|
626
729
|
};
|
|
627
730
|
}
|
|
628
731
|
/**
|
|
@@ -747,6 +850,7 @@ class SocketService extends EventEmitter {
|
|
|
747
850
|
this.heartbeatInterval = null;
|
|
748
851
|
this.messageQueue = [];
|
|
749
852
|
this.maxQueueSize = 100;
|
|
853
|
+
this.maxReconnectAttempts = 10;
|
|
750
854
|
this.isIntentionallyClosed = false;
|
|
751
855
|
this.useBinaryProtocol = false;
|
|
752
856
|
this.authService = new AuthService();
|
|
@@ -846,8 +950,22 @@ class SocketService extends EventEmitter {
|
|
|
846
950
|
log$c.info("Auth failed, clearing token");
|
|
847
951
|
this.authService.clearToken();
|
|
848
952
|
}
|
|
849
|
-
if (!this.isIntentionallyClosed
|
|
850
|
-
this.
|
|
953
|
+
if (!this.isIntentionallyClosed) {
|
|
954
|
+
const maxAttempts = Math.min(CONFIG.websocket.reconnectAttempts, this.maxReconnectAttempts);
|
|
955
|
+
if (this.reconnectAttempts < maxAttempts) {
|
|
956
|
+
this.scheduleReconnect();
|
|
957
|
+
} else {
|
|
958
|
+
log$c.error(`Max reconnection attempts (${maxAttempts}) reached. Giving up.`);
|
|
959
|
+
this.setConnectionState("error");
|
|
960
|
+
this.emit("connection-failed", {
|
|
961
|
+
reason: "max-retries",
|
|
962
|
+
attempts: this.reconnectAttempts
|
|
963
|
+
});
|
|
964
|
+
errorBoundary.handleError(
|
|
965
|
+
new Error(`Failed to connect after ${maxAttempts} attempts`),
|
|
966
|
+
"websocket"
|
|
967
|
+
);
|
|
968
|
+
}
|
|
851
969
|
}
|
|
852
970
|
}
|
|
853
971
|
scheduleReconnect() {
|
|
@@ -856,7 +974,8 @@ class SocketService extends EventEmitter {
|
|
|
856
974
|
}
|
|
857
975
|
this.setConnectionState("reconnecting");
|
|
858
976
|
const delay = this.calculateReconnectDelay();
|
|
859
|
-
|
|
977
|
+
const maxAttempts = Math.min(CONFIG.websocket.reconnectAttempts, this.maxReconnectAttempts);
|
|
978
|
+
log$c.info(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts + 1}/${maxAttempts})`);
|
|
860
979
|
this.reconnectTimeout = window.setTimeout(() => {
|
|
861
980
|
this.reconnectTimeout = null;
|
|
862
981
|
this.reconnectAttempts++;
|
|
@@ -941,6 +1060,17 @@ class SocketService extends EventEmitter {
|
|
|
941
1060
|
this.emit("connectionStateChanged", state);
|
|
942
1061
|
}
|
|
943
1062
|
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Manually reconnect to the server
|
|
1065
|
+
* Resets reconnection counter and attempts immediate connection
|
|
1066
|
+
*/
|
|
1067
|
+
async reconnect() {
|
|
1068
|
+
log$c.info("Manual reconnect requested");
|
|
1069
|
+
this.disconnect();
|
|
1070
|
+
this.reconnectAttempts = 0;
|
|
1071
|
+
this.isIntentionallyClosed = false;
|
|
1072
|
+
return this.connect();
|
|
1073
|
+
}
|
|
944
1074
|
disconnect() {
|
|
945
1075
|
this.isIntentionallyClosed = true;
|
|
946
1076
|
if (this.reconnectTimeout !== null) {
|
|
@@ -1071,7 +1201,9 @@ class AudioInput {
|
|
|
1071
1201
|
if (!this.audioContext || !this.sourceNode) {
|
|
1072
1202
|
throw new Error("AudioContext not initialized");
|
|
1073
1203
|
}
|
|
1074
|
-
|
|
1204
|
+
const baseUrl = CONFIG.assets.baseUrl || "";
|
|
1205
|
+
const workletUrl = baseUrl ? `${baseUrl}/pcm16-processor.worklet.js` : "/pcm16-processor.worklet.js";
|
|
1206
|
+
await this.audioContext.audioWorklet.addModule(workletUrl);
|
|
1075
1207
|
this.workletNode = new AudioWorkletNode(this.audioContext, "pcm16-processor", {
|
|
1076
1208
|
numberOfInputs: 1,
|
|
1077
1209
|
numberOfOutputs: 1,
|
|
@@ -1188,7 +1320,7 @@ class AudioInput {
|
|
|
1188
1320
|
};
|
|
1189
1321
|
this.mediaRecorder.onerror = (event) => {
|
|
1190
1322
|
errorBoundary.handleError(
|
|
1191
|
-
new Error(`MediaRecorder error: ${event.error}`),
|
|
1323
|
+
new Error(`MediaRecorder error: ${event.error || "Unknown error"}`),
|
|
1192
1324
|
"audio-input"
|
|
1193
1325
|
);
|
|
1194
1326
|
};
|
|
@@ -1538,6 +1670,7 @@ const _AudioContextManagerImpl = class _AudioContextManagerImpl {
|
|
|
1538
1670
|
this._context = null;
|
|
1539
1671
|
this._isResumeListenerAdded = false;
|
|
1540
1672
|
this._resumePromise = null;
|
|
1673
|
+
this._suspendPromise = null;
|
|
1541
1674
|
this._sampleRate = 24e3;
|
|
1542
1675
|
}
|
|
1543
1676
|
static getInstance() {
|
|
@@ -1559,6 +1692,9 @@ const _AudioContextManagerImpl = class _AudioContextManagerImpl {
|
|
|
1559
1692
|
}
|
|
1560
1693
|
try {
|
|
1561
1694
|
const AudioContextClass = window.AudioContext || window.webkitAudioContext;
|
|
1695
|
+
if (!AudioContextClass) {
|
|
1696
|
+
throw new Error("AudioContext not supported in this browser");
|
|
1697
|
+
}
|
|
1562
1698
|
this._context = new AudioContextClass({
|
|
1563
1699
|
sampleRate: this._sampleRate,
|
|
1564
1700
|
latencyHint: "interactive"
|
|
@@ -1604,6 +1740,7 @@ const _AudioContextManagerImpl = class _AudioContextManagerImpl {
|
|
|
1604
1740
|
}
|
|
1605
1741
|
/**
|
|
1606
1742
|
* Resume the AudioContext (call after user interaction)
|
|
1743
|
+
* Race-condition safe: multiple calls will share the same promise
|
|
1607
1744
|
*/
|
|
1608
1745
|
async resume() {
|
|
1609
1746
|
if (!this._context) {
|
|
@@ -1615,28 +1752,45 @@ const _AudioContextManagerImpl = class _AudioContextManagerImpl {
|
|
|
1615
1752
|
if (this._resumePromise) {
|
|
1616
1753
|
return this._resumePromise;
|
|
1617
1754
|
}
|
|
1618
|
-
this._resumePromise =
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1755
|
+
this._resumePromise = (async () => {
|
|
1756
|
+
try {
|
|
1757
|
+
await this._context.resume();
|
|
1758
|
+
log$9.info("AudioContext resumed successfully");
|
|
1759
|
+
} catch (error2) {
|
|
1760
|
+
log$9.error("Failed to resume AudioContext:", error2);
|
|
1761
|
+
errorBoundary.handleError(error2, "audio-context-manager");
|
|
1762
|
+
throw error2;
|
|
1763
|
+
} finally {
|
|
1764
|
+
this._resumePromise = null;
|
|
1765
|
+
}
|
|
1766
|
+
})();
|
|
1626
1767
|
return this._resumePromise;
|
|
1627
1768
|
}
|
|
1628
1769
|
/**
|
|
1629
1770
|
* Suspend the AudioContext (save resources when not needed)
|
|
1771
|
+
* Race-condition safe: multiple calls will share the same promise
|
|
1630
1772
|
*/
|
|
1631
1773
|
async suspend() {
|
|
1632
|
-
if (this._context
|
|
1774
|
+
if (!this._context) {
|
|
1775
|
+
return;
|
|
1776
|
+
}
|
|
1777
|
+
if (this._context.state === "suspended") {
|
|
1778
|
+
return;
|
|
1779
|
+
}
|
|
1780
|
+
if (this._suspendPromise) {
|
|
1781
|
+
return this._suspendPromise;
|
|
1782
|
+
}
|
|
1783
|
+
this._suspendPromise = (async () => {
|
|
1633
1784
|
try {
|
|
1634
1785
|
await this._context.suspend();
|
|
1635
1786
|
log$9.debug("AudioContext suspended");
|
|
1636
1787
|
} catch (error2) {
|
|
1637
1788
|
log$9.error("Failed to suspend AudioContext:", error2);
|
|
1789
|
+
} finally {
|
|
1790
|
+
this._suspendPromise = null;
|
|
1638
1791
|
}
|
|
1639
|
-
}
|
|
1792
|
+
})();
|
|
1793
|
+
return this._suspendPromise;
|
|
1640
1794
|
}
|
|
1641
1795
|
/**
|
|
1642
1796
|
* Get current AudioContext state
|
|
@@ -2596,6 +2750,7 @@ class ChatManager {
|
|
|
2596
2750
|
this.isRecording = false;
|
|
2597
2751
|
this.animationFrameId = null;
|
|
2598
2752
|
this.useSyncPlayback = false;
|
|
2753
|
+
this.currentStreamingMessage = null;
|
|
2599
2754
|
this.avatar = avatar;
|
|
2600
2755
|
this.options = options;
|
|
2601
2756
|
this.userId = this.generateUserId();
|
|
@@ -2754,21 +2909,21 @@ class ChatManager {
|
|
|
2754
2909
|
this.syncPlayback.stop();
|
|
2755
2910
|
this.audioOutput.stop();
|
|
2756
2911
|
this.blendshapeBuffer.clear();
|
|
2912
|
+
this.finalizeStreamingMessage();
|
|
2757
2913
|
this.avatar.disableLiveBlendshapes();
|
|
2758
2914
|
this.avatar.setChatState("Hello");
|
|
2759
2915
|
}
|
|
2760
2916
|
});
|
|
2761
2917
|
this.socketService.on("transcript_delta", (message) => {
|
|
2762
|
-
if (message.type === "transcript_delta") {
|
|
2763
|
-
|
|
2918
|
+
if (message.type === "transcript_delta" && message.text) {
|
|
2919
|
+
const role = message.role === "assistant" ? "assistant" : "user";
|
|
2920
|
+
this.streamTranscript(message.text, role);
|
|
2764
2921
|
}
|
|
2765
2922
|
});
|
|
2766
2923
|
this.socketService.on("transcript_done", (message) => {
|
|
2767
2924
|
if (message.type === "transcript_done") {
|
|
2768
2925
|
log$3.debug(`Transcript complete [${message.role}]: ${message.text}`);
|
|
2769
|
-
|
|
2770
|
-
this.addMessage(message.text, message.role === "assistant" ? "assistant" : "user");
|
|
2771
|
-
}
|
|
2926
|
+
this.finalizeStreamingMessage();
|
|
2772
2927
|
}
|
|
2773
2928
|
});
|
|
2774
2929
|
this.socketService.on("error", (error2) => {
|
|
@@ -2891,6 +3046,60 @@ class ChatManager {
|
|
|
2891
3046
|
this.scrollToBottom();
|
|
2892
3047
|
this.options.onMessage?.({ role: sender, text });
|
|
2893
3048
|
}
|
|
3049
|
+
/**
|
|
3050
|
+
* Stream transcript text in real-time (word by word)
|
|
3051
|
+
*/
|
|
3052
|
+
streamTranscript(text, role) {
|
|
3053
|
+
if (!this.currentStreamingMessage || this.currentStreamingMessage.role !== role) {
|
|
3054
|
+
const messageId = Date.now().toString();
|
|
3055
|
+
const messageEl = document.createElement("div");
|
|
3056
|
+
messageEl.className = `message ${role}`;
|
|
3057
|
+
messageEl.dataset.id = messageId;
|
|
3058
|
+
const bubbleEl = document.createElement("div");
|
|
3059
|
+
bubbleEl.className = "message-bubble";
|
|
3060
|
+
bubbleEl.textContent = text;
|
|
3061
|
+
const timeEl = document.createElement("div");
|
|
3062
|
+
timeEl.className = "message-time";
|
|
3063
|
+
timeEl.textContent = (/* @__PURE__ */ new Date()).toLocaleTimeString([], {
|
|
3064
|
+
hour: "2-digit",
|
|
3065
|
+
minute: "2-digit"
|
|
3066
|
+
});
|
|
3067
|
+
messageEl.appendChild(bubbleEl);
|
|
3068
|
+
messageEl.appendChild(timeEl);
|
|
3069
|
+
this.chatMessages.appendChild(messageEl);
|
|
3070
|
+
this.currentStreamingMessage = {
|
|
3071
|
+
id: messageId,
|
|
3072
|
+
element: messageEl,
|
|
3073
|
+
role
|
|
3074
|
+
};
|
|
3075
|
+
this.scrollToBottom();
|
|
3076
|
+
} else {
|
|
3077
|
+
const bubbleEl = this.currentStreamingMessage.element.querySelector(".message-bubble");
|
|
3078
|
+
if (bubbleEl) {
|
|
3079
|
+
bubbleEl.textContent += text;
|
|
3080
|
+
this.scrollToBottom();
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
/**
|
|
3085
|
+
* Finalize the streaming message and add it to messages array
|
|
3086
|
+
*/
|
|
3087
|
+
finalizeStreamingMessage() {
|
|
3088
|
+
if (!this.currentStreamingMessage) return;
|
|
3089
|
+
const bubbleEl = this.currentStreamingMessage.element.querySelector(".message-bubble");
|
|
3090
|
+
const text = bubbleEl?.textContent || "";
|
|
3091
|
+
if (text) {
|
|
3092
|
+
const message = {
|
|
3093
|
+
id: this.currentStreamingMessage.id,
|
|
3094
|
+
text,
|
|
3095
|
+
sender: this.currentStreamingMessage.role,
|
|
3096
|
+
timestamp: Date.now()
|
|
3097
|
+
};
|
|
3098
|
+
this.messages.push(message);
|
|
3099
|
+
this.options.onMessage?.({ role: this.currentStreamingMessage.role, text });
|
|
3100
|
+
}
|
|
3101
|
+
this.currentStreamingMessage = null;
|
|
3102
|
+
}
|
|
2894
3103
|
renderMessage(message) {
|
|
2895
3104
|
const messageEl = document.createElement("div");
|
|
2896
3105
|
messageEl.className = `message ${message.sender}`;
|
|
@@ -2919,30 +3128,51 @@ class ChatManager {
|
|
|
2919
3128
|
this.resetOnMinimize();
|
|
2920
3129
|
} else {
|
|
2921
3130
|
bubble?.classList.add("hidden");
|
|
3131
|
+
this.reconnectOnExpand();
|
|
2922
3132
|
}
|
|
2923
3133
|
}
|
|
3134
|
+
/**
|
|
3135
|
+
* Reset chat state when minimizing (public API for widget)
|
|
3136
|
+
*/
|
|
2924
3137
|
resetOnMinimize() {
|
|
3138
|
+
log$3.info("Minimizing widget - stopping all activity");
|
|
2925
3139
|
if (this.isRecording) {
|
|
3140
|
+
log$3.debug("Stopping recording...");
|
|
2926
3141
|
this.stopRecording();
|
|
2927
3142
|
}
|
|
3143
|
+
log$3.debug("Stopping audio playback and clearing buffers...");
|
|
2928
3144
|
this.syncPlayback.stop();
|
|
2929
3145
|
this.audioOutput.stop();
|
|
2930
3146
|
this.blendshapeBuffer.clear();
|
|
2931
3147
|
this.avatar.disableLiveBlendshapes();
|
|
3148
|
+
log$3.debug("Disconnecting from server...");
|
|
3149
|
+
this.socketService.disconnect();
|
|
2932
3150
|
this.avatar.setChatState("Idle");
|
|
2933
|
-
if ("pause" in this.avatar) {
|
|
3151
|
+
if ("pause" in this.avatar && typeof this.avatar.pause === "function") {
|
|
2934
3152
|
this.avatar.pause();
|
|
2935
3153
|
}
|
|
2936
|
-
log$3.
|
|
3154
|
+
log$3.info("Widget minimized - all activity stopped");
|
|
3155
|
+
}
|
|
3156
|
+
/**
|
|
3157
|
+
* Reconnect to server when expanding (public API for widget)
|
|
3158
|
+
*/
|
|
3159
|
+
async reconnectOnExpand() {
|
|
3160
|
+
try {
|
|
3161
|
+
await this.socketService.connect();
|
|
3162
|
+
log$3.debug("Chat expanded - reconnected to server");
|
|
3163
|
+
} catch (error2) {
|
|
3164
|
+
log$3.error("Failed to reconnect on expand:", error2);
|
|
3165
|
+
}
|
|
3166
|
+
if ("resume" in this.avatar && typeof this.avatar.resume === "function") {
|
|
3167
|
+
this.avatar.resume();
|
|
3168
|
+
}
|
|
2937
3169
|
}
|
|
2938
3170
|
openChat() {
|
|
2939
3171
|
const widget = document.getElementById("chatWidget");
|
|
2940
3172
|
const bubble = document.getElementById("chatBubble");
|
|
2941
3173
|
widget?.classList.remove("minimized");
|
|
2942
3174
|
bubble?.classList.add("hidden");
|
|
2943
|
-
|
|
2944
|
-
this.avatar.resume();
|
|
2945
|
-
}
|
|
3175
|
+
this.reconnectOnExpand();
|
|
2946
3176
|
this.chatInput?.focus();
|
|
2947
3177
|
}
|
|
2948
3178
|
generateUserId() {
|
|
@@ -2961,6 +3191,12 @@ class ChatManager {
|
|
|
2961
3191
|
}
|
|
2962
3192
|
return bytes.buffer;
|
|
2963
3193
|
}
|
|
3194
|
+
/**
|
|
3195
|
+
* Manually reconnect to the server
|
|
3196
|
+
*/
|
|
3197
|
+
async reconnect() {
|
|
3198
|
+
return this.socketService.reconnect();
|
|
3199
|
+
}
|
|
2964
3200
|
dispose() {
|
|
2965
3201
|
if (this.animationFrameId !== null) {
|
|
2966
3202
|
cancelAnimationFrame(this.animationFrameId);
|
|
@@ -2972,19 +3208,6 @@ class ChatManager {
|
|
|
2972
3208
|
this.syncPlayback.dispose();
|
|
2973
3209
|
}
|
|
2974
3210
|
}
|
|
2975
|
-
const log$2 = logger.scope("Widget");
|
|
2976
|
-
const DEFAULT_CONFIG = {
|
|
2977
|
-
position: "bottom-right",
|
|
2978
|
-
theme: "light",
|
|
2979
|
-
startCollapsed: true,
|
|
2980
|
-
width: 380,
|
|
2981
|
-
height: 550,
|
|
2982
|
-
enableVoice: true,
|
|
2983
|
-
enableText: true,
|
|
2984
|
-
avatarUrl: "./asset/nyx.zip",
|
|
2985
|
-
authEnabled: true,
|
|
2986
|
-
logLevel: "error"
|
|
2987
|
-
};
|
|
2988
3211
|
const WIDGET_STYLES = `
|
|
2989
3212
|
/* Reset all inherited styles */
|
|
2990
3213
|
:host {
|
|
@@ -3015,11 +3238,18 @@ const WIDGET_STYLES = `
|
|
|
3015
3238
|
height: 100%;
|
|
3016
3239
|
display: flex;
|
|
3017
3240
|
flex-direction: column;
|
|
3018
|
-
background:
|
|
3019
|
-
border-radius:
|
|
3020
|
-
box-shadow: 0
|
|
3021
|
-
overflow:
|
|
3022
|
-
transition:
|
|
3241
|
+
background: white;
|
|
3242
|
+
border-radius: 12px;
|
|
3243
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
|
3244
|
+
overflow: visible;
|
|
3245
|
+
transition: transform 0.3s ease;
|
|
3246
|
+
position: relative;
|
|
3247
|
+
}
|
|
3248
|
+
|
|
3249
|
+
.widget-root.minimized {
|
|
3250
|
+
transform: scale(0);
|
|
3251
|
+
opacity: 0;
|
|
3252
|
+
pointer-events: none;
|
|
3023
3253
|
}
|
|
3024
3254
|
|
|
3025
3255
|
.widget-root.theme-dark {
|
|
@@ -3029,285 +3259,305 @@ const WIDGET_STYLES = `
|
|
|
3029
3259
|
|
|
3030
3260
|
/* Collapsed bubble state */
|
|
3031
3261
|
:host(.collapsed) {
|
|
3032
|
-
width:
|
|
3033
|
-
height:
|
|
3262
|
+
width: 60px !important;
|
|
3263
|
+
height: 60px !important;
|
|
3034
3264
|
}
|
|
3035
3265
|
|
|
3036
3266
|
.chat-bubble {
|
|
3037
|
-
width:
|
|
3038
|
-
height:
|
|
3267
|
+
width: 60px;
|
|
3268
|
+
height: 60px;
|
|
3039
3269
|
border-radius: 50%;
|
|
3040
3270
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
3271
|
+
box-shadow: 0 4px 20px rgba(102, 126, 234, 0.4);
|
|
3272
|
+
cursor: pointer;
|
|
3041
3273
|
display: flex;
|
|
3042
3274
|
align-items: center;
|
|
3043
3275
|
justify-content: center;
|
|
3044
|
-
|
|
3045
|
-
box-shadow: 0 4px 16px rgba(102, 126, 234, 0.4);
|
|
3046
|
-
transition: transform 0.2s, box-shadow 0.2s;
|
|
3276
|
+
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
3047
3277
|
}
|
|
3048
3278
|
|
|
3049
3279
|
.chat-bubble:hover {
|
|
3050
|
-
transform: scale(1.
|
|
3051
|
-
box-shadow: 0 6px
|
|
3280
|
+
transform: scale(1.1);
|
|
3281
|
+
box-shadow: 0 6px 25px rgba(102, 126, 234, 0.5);
|
|
3282
|
+
}
|
|
3283
|
+
|
|
3284
|
+
.chat-bubble.hidden {
|
|
3285
|
+
transform: scale(0);
|
|
3286
|
+
opacity: 0;
|
|
3287
|
+
pointer-events: none;
|
|
3052
3288
|
}
|
|
3053
3289
|
|
|
3054
3290
|
.chat-bubble svg {
|
|
3055
3291
|
width: 28px;
|
|
3056
3292
|
height: 28px;
|
|
3057
|
-
|
|
3293
|
+
color: white;
|
|
3058
3294
|
}
|
|
3059
3295
|
|
|
3060
|
-
/* Avatar
|
|
3061
|
-
.avatar-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
background:
|
|
3296
|
+
/* Avatar Circle - Breaking out at top */
|
|
3297
|
+
.avatar-circle {
|
|
3298
|
+
width: 120px;
|
|
3299
|
+
height: 120px;
|
|
3300
|
+
border-radius: 50%;
|
|
3301
|
+
background: #ffffff;
|
|
3302
|
+
border: 3px solid #667eea;
|
|
3066
3303
|
overflow: hidden;
|
|
3304
|
+
position: absolute;
|
|
3305
|
+
left: -35px;
|
|
3306
|
+
top: -40px;
|
|
3307
|
+
z-index: 1001;
|
|
3308
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25);
|
|
3309
|
+
transition: opacity 0.3s ease, transform 0.3s ease;
|
|
3067
3310
|
}
|
|
3068
3311
|
|
|
3069
|
-
.
|
|
3070
|
-
|
|
3312
|
+
.widget-root.minimized .avatar-circle {
|
|
3313
|
+
opacity: 0;
|
|
3314
|
+
pointer-events: none;
|
|
3315
|
+
transform: scale(0.5);
|
|
3071
3316
|
}
|
|
3072
3317
|
|
|
3073
|
-
.avatar-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
position: relative;
|
|
3318
|
+
.theme-dark .avatar-circle {
|
|
3319
|
+
background: #1a1a2e;
|
|
3320
|
+
border-color: #764ba2;
|
|
3077
3321
|
}
|
|
3078
3322
|
|
|
3323
|
+
/* Avatar render container - high-res rendering scaled down */
|
|
3079
3324
|
.avatar-render-container {
|
|
3080
|
-
width:
|
|
3081
|
-
height:
|
|
3325
|
+
width: 800px;
|
|
3326
|
+
height: 800px;
|
|
3082
3327
|
position: absolute;
|
|
3083
|
-
top:
|
|
3328
|
+
top: -80px;
|
|
3084
3329
|
left: 50%;
|
|
3085
|
-
transform:
|
|
3086
|
-
transform-origin: center;
|
|
3087
|
-
/* OPTIMIZATION: Hint browser to optimize for transform animations */
|
|
3330
|
+
transform: translateX(-50%) scale(0.38);
|
|
3331
|
+
transform-origin: center top;
|
|
3088
3332
|
will-change: transform;
|
|
3089
3333
|
}
|
|
3090
3334
|
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
justify-content: space-between;
|
|
3096
|
-
padding: 10px 16px;
|
|
3097
|
-
background: rgba(255, 255, 255, 0.95);
|
|
3098
|
-
border-bottom: 1px solid #eee;
|
|
3335
|
+
.avatar-render-container canvas {
|
|
3336
|
+
width: 800px !important;
|
|
3337
|
+
height: 800px !important;
|
|
3338
|
+
object-fit: contain;
|
|
3099
3339
|
}
|
|
3100
3340
|
|
|
3101
|
-
.
|
|
3102
|
-
|
|
3103
|
-
|
|
3341
|
+
.avatar-circle > canvas {
|
|
3342
|
+
width: 800px !important;
|
|
3343
|
+
height: 800px !important;
|
|
3344
|
+
position: absolute;
|
|
3345
|
+
top: -80px;
|
|
3346
|
+
left: 50%;
|
|
3347
|
+
transform: translateX(-50%) scale(0.38);
|
|
3348
|
+
transform-origin: center top;
|
|
3349
|
+
object-fit: contain;
|
|
3104
3350
|
}
|
|
3105
3351
|
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
color: #666;
|
|
3352
|
+
/* Speaking indicator */
|
|
3353
|
+
.avatar-circle.speaking {
|
|
3354
|
+
border-color: #27ae60;
|
|
3355
|
+
box-shadow: 0 6px 30px rgba(0, 0, 0, 0.3), 0 0 0 5px rgba(39, 174, 96, 0.3);
|
|
3356
|
+
animation: speakPulse 1s infinite;
|
|
3112
3357
|
}
|
|
3113
3358
|
|
|
3114
|
-
|
|
3359
|
+
@keyframes speakPulse {
|
|
3360
|
+
0%, 100% { box-shadow: 0 6px 30px rgba(0, 0, 0, 0.3), 0 0 0 5px rgba(39, 174, 96, 0.3); }
|
|
3361
|
+
50% { box-shadow: 0 6px 30px rgba(0, 0, 0, 0.3), 0 0 0 10px rgba(39, 174, 96, 0.1); }
|
|
3362
|
+
}
|
|
3115
3363
|
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3364
|
+
/* Chat Header */
|
|
3365
|
+
.chat-header {
|
|
3366
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
3367
|
+
color: white;
|
|
3368
|
+
padding: 14px;
|
|
3369
|
+
padding-left: 95px;
|
|
3370
|
+
border-radius: 12px 12px 0 0;
|
|
3371
|
+
display: flex;
|
|
3372
|
+
justify-content: space-between;
|
|
3373
|
+
align-items: center;
|
|
3374
|
+
cursor: pointer;
|
|
3375
|
+
position: relative;
|
|
3122
3376
|
}
|
|
3123
3377
|
|
|
3124
|
-
.
|
|
3125
|
-
|
|
3126
|
-
|
|
3378
|
+
.theme-dark .chat-header {
|
|
3379
|
+
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
|
3380
|
+
}
|
|
3127
3381
|
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3382
|
+
.chat-header h3 {
|
|
3383
|
+
font-size: 15px;
|
|
3384
|
+
font-weight: 600;
|
|
3385
|
+
margin: 0;
|
|
3131
3386
|
}
|
|
3132
3387
|
|
|
3133
|
-
.header-
|
|
3388
|
+
.chat-header-buttons {
|
|
3134
3389
|
display: flex;
|
|
3135
|
-
gap:
|
|
3390
|
+
gap: 8px;
|
|
3136
3391
|
}
|
|
3137
3392
|
|
|
3138
|
-
.
|
|
3139
|
-
background:
|
|
3393
|
+
.chat-header-buttons button {
|
|
3394
|
+
background: rgba(255, 255, 255, 0.2);
|
|
3140
3395
|
border: none;
|
|
3396
|
+
color: white;
|
|
3397
|
+
width: 28px;
|
|
3398
|
+
height: 28px;
|
|
3399
|
+
border-radius: 50%;
|
|
3141
3400
|
cursor: pointer;
|
|
3142
|
-
|
|
3143
|
-
border-radius: 6px;
|
|
3144
|
-
color: #666;
|
|
3145
|
-
transition: background 0.2s;
|
|
3401
|
+
font-size: 14px;
|
|
3146
3402
|
display: flex;
|
|
3147
3403
|
align-items: center;
|
|
3148
3404
|
justify-content: center;
|
|
3405
|
+
transition: background 0.2s;
|
|
3149
3406
|
}
|
|
3150
3407
|
|
|
3151
|
-
.
|
|
3152
|
-
|
|
3153
|
-
|
|
3408
|
+
.chat-header-buttons button:hover {
|
|
3409
|
+
background: rgba(255, 255, 255, 0.3);
|
|
3410
|
+
}
|
|
3154
3411
|
|
|
3155
|
-
/* Messages */
|
|
3156
|
-
.messages
|
|
3412
|
+
/* Messages Container */
|
|
3413
|
+
.chat-messages {
|
|
3157
3414
|
flex: 1;
|
|
3158
3415
|
overflow-y: auto;
|
|
3159
|
-
padding:
|
|
3416
|
+
padding: 14px;
|
|
3417
|
+
min-height: 250px;
|
|
3418
|
+
max-height: 320px;
|
|
3419
|
+
background: #fafafa;
|
|
3420
|
+
}
|
|
3421
|
+
|
|
3422
|
+
.theme-dark .chat-messages {
|
|
3423
|
+
background: #16213e;
|
|
3424
|
+
}
|
|
3425
|
+
|
|
3426
|
+
.message {
|
|
3427
|
+
margin-bottom: 16px;
|
|
3160
3428
|
display: flex;
|
|
3161
3429
|
flex-direction: column;
|
|
3162
|
-
|
|
3163
|
-
min-height: 0;
|
|
3430
|
+
animation: fadeIn 0.3s ease;
|
|
3164
3431
|
}
|
|
3165
3432
|
|
|
3166
|
-
|
|
3167
|
-
|
|
3433
|
+
@keyframes fadeIn {
|
|
3434
|
+
from { opacity: 0; transform: translateY(10px); }
|
|
3435
|
+
to { opacity: 1; transform: translateY(0); }
|
|
3436
|
+
}
|
|
3437
|
+
|
|
3438
|
+
.message.user {
|
|
3439
|
+
align-items: flex-end;
|
|
3440
|
+
}
|
|
3441
|
+
|
|
3442
|
+
.message.assistant {
|
|
3443
|
+
align-items: flex-start;
|
|
3444
|
+
}
|
|
3445
|
+
|
|
3446
|
+
.message-bubble {
|
|
3447
|
+
max-width: 80%;
|
|
3168
3448
|
padding: 10px 14px;
|
|
3169
3449
|
border-radius: 16px;
|
|
3170
3450
|
word-wrap: break-word;
|
|
3171
3451
|
font-size: 14px;
|
|
3172
|
-
line-height: 1.
|
|
3452
|
+
line-height: 1.45;
|
|
3173
3453
|
}
|
|
3174
3454
|
|
|
3175
|
-
.message.user {
|
|
3176
|
-
|
|
3177
|
-
background: #007bff;
|
|
3455
|
+
.message.user .message-bubble {
|
|
3456
|
+
background: #667eea;
|
|
3178
3457
|
color: white;
|
|
3179
3458
|
border-bottom-right-radius: 4px;
|
|
3180
3459
|
}
|
|
3181
3460
|
|
|
3182
|
-
.message.assistant {
|
|
3183
|
-
|
|
3184
|
-
background: #f0f0f0;
|
|
3461
|
+
.message.assistant .message-bubble {
|
|
3462
|
+
background: white;
|
|
3185
3463
|
color: #333;
|
|
3464
|
+
border: 1px solid #e0e0e0;
|
|
3186
3465
|
border-bottom-left-radius: 4px;
|
|
3187
3466
|
}
|
|
3188
3467
|
|
|
3189
|
-
.theme-dark .message.assistant {
|
|
3190
|
-
background: #
|
|
3468
|
+
.theme-dark .message.assistant .message-bubble {
|
|
3469
|
+
background: #1a1a2e;
|
|
3191
3470
|
color: #e0e0e0;
|
|
3471
|
+
border-color: #333;
|
|
3192
3472
|
}
|
|
3193
3473
|
|
|
3194
3474
|
.message-time {
|
|
3195
|
-
font-size:
|
|
3196
|
-
|
|
3197
|
-
margin-top:
|
|
3475
|
+
font-size: 11px;
|
|
3476
|
+
color: #999;
|
|
3477
|
+
margin-top: 6px;
|
|
3478
|
+
padding: 0 4px;
|
|
3198
3479
|
}
|
|
3199
3480
|
|
|
3200
|
-
/* Input
|
|
3201
|
-
.input-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
border-
|
|
3206
|
-
background: #fafafa;
|
|
3481
|
+
/* Input Area */
|
|
3482
|
+
.chat-input-area {
|
|
3483
|
+
padding: 12px;
|
|
3484
|
+
background: white;
|
|
3485
|
+
border-top: 1px solid #e0e0e0;
|
|
3486
|
+
border-radius: 0 0 12px 12px;
|
|
3207
3487
|
}
|
|
3208
3488
|
|
|
3209
|
-
.theme-dark .input-
|
|
3210
|
-
background: #
|
|
3489
|
+
.theme-dark .chat-input-area {
|
|
3490
|
+
background: #1a1a2e;
|
|
3211
3491
|
border-top-color: #333;
|
|
3212
3492
|
}
|
|
3213
3493
|
|
|
3214
|
-
.chat-input {
|
|
3494
|
+
.chat-input-wrapper {
|
|
3495
|
+
display: flex;
|
|
3496
|
+
gap: 10px;
|
|
3497
|
+
align-items: flex-end;
|
|
3498
|
+
}
|
|
3499
|
+
|
|
3500
|
+
.chat-input-controls {
|
|
3501
|
+
display: flex;
|
|
3502
|
+
gap: 6px;
|
|
3503
|
+
align-items: center;
|
|
3504
|
+
flex: 1;
|
|
3505
|
+
}
|
|
3506
|
+
|
|
3507
|
+
#chatInput {
|
|
3215
3508
|
flex: 1;
|
|
3216
3509
|
padding: 10px 14px;
|
|
3217
|
-
border: 1px solid #
|
|
3218
|
-
border-radius:
|
|
3219
|
-
outline: none;
|
|
3510
|
+
border: 1px solid #e0e0e0;
|
|
3511
|
+
border-radius: 20px;
|
|
3220
3512
|
font-size: 14px;
|
|
3513
|
+
outline: none;
|
|
3514
|
+
transition: border-color 0.2s;
|
|
3221
3515
|
font-family: inherit;
|
|
3222
|
-
transition: border-color 0.2s, box-shadow 0.2s;
|
|
3223
3516
|
}
|
|
3224
3517
|
|
|
3225
|
-
|
|
3226
|
-
border-color: #
|
|
3227
|
-
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1);
|
|
3518
|
+
#chatInput:focus {
|
|
3519
|
+
border-color: #667eea;
|
|
3228
3520
|
}
|
|
3229
3521
|
|
|
3230
|
-
.theme-dark
|
|
3231
|
-
background: #
|
|
3232
|
-
border-color: #
|
|
3522
|
+
.theme-dark #chatInput {
|
|
3523
|
+
background: #16213e;
|
|
3524
|
+
border-color: #333;
|
|
3233
3525
|
color: #e0e0e0;
|
|
3234
3526
|
}
|
|
3235
3527
|
|
|
3236
|
-
.
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
}
|
|
3240
|
-
|
|
3241
|
-
.action-btn {
|
|
3242
|
-
width: 40px;
|
|
3243
|
-
height: 40px;
|
|
3244
|
-
border: none;
|
|
3528
|
+
.input-button {
|
|
3529
|
+
width: 38px;
|
|
3530
|
+
height: 38px;
|
|
3245
3531
|
border-radius: 50%;
|
|
3532
|
+
border: none;
|
|
3533
|
+
background: #667eea;
|
|
3534
|
+
color: white;
|
|
3246
3535
|
cursor: pointer;
|
|
3247
3536
|
display: flex;
|
|
3248
3537
|
align-items: center;
|
|
3249
3538
|
justify-content: center;
|
|
3250
|
-
transition:
|
|
3539
|
+
transition: all 0.2s;
|
|
3251
3540
|
flex-shrink: 0;
|
|
3252
3541
|
}
|
|
3253
3542
|
|
|
3254
|
-
.
|
|
3255
|
-
|
|
3256
|
-
.
|
|
3257
|
-
background: #f0f0f0;
|
|
3258
|
-
color: #666;
|
|
3259
|
-
}
|
|
3260
|
-
|
|
3261
|
-
.voice-btn:hover { background: #e0e0e0; }
|
|
3262
|
-
|
|
3263
|
-
.voice-btn.recording {
|
|
3264
|
-
background: #ff4444;
|
|
3265
|
-
color: white;
|
|
3266
|
-
animation: pulse 1s infinite;
|
|
3267
|
-
}
|
|
3268
|
-
|
|
3269
|
-
.theme-dark .voice-btn {
|
|
3270
|
-
background: #2d2d44;
|
|
3271
|
-
color: #aaa;
|
|
3272
|
-
}
|
|
3273
|
-
|
|
3274
|
-
.theme-dark .voice-btn:hover { background: #3d3d54; }
|
|
3275
|
-
|
|
3276
|
-
.send-btn {
|
|
3277
|
-
background: #007bff;
|
|
3278
|
-
color: white;
|
|
3279
|
-
}
|
|
3280
|
-
|
|
3281
|
-
.send-btn:hover { background: #0056b3; }
|
|
3282
|
-
.send-btn:disabled { background: #ccc; cursor: not-allowed; }
|
|
3283
|
-
|
|
3284
|
-
/* Loading states */
|
|
3285
|
-
.loading-overlay {
|
|
3286
|
-
position: absolute;
|
|
3287
|
-
inset: 0;
|
|
3288
|
-
background: rgba(255, 255, 255, 0.9);
|
|
3289
|
-
display: flex;
|
|
3290
|
-
align-items: center;
|
|
3291
|
-
justify-content: center;
|
|
3292
|
-
flex-direction: column;
|
|
3293
|
-
gap: 12px;
|
|
3543
|
+
.input-button:hover:not(:disabled) {
|
|
3544
|
+
background: #5568d3;
|
|
3545
|
+
transform: scale(1.05);
|
|
3294
3546
|
}
|
|
3295
3547
|
|
|
3296
|
-
.
|
|
3297
|
-
background:
|
|
3548
|
+
.input-button:disabled {
|
|
3549
|
+
background: #ccc;
|
|
3550
|
+
cursor: not-allowed;
|
|
3298
3551
|
}
|
|
3299
3552
|
|
|
3300
|
-
.
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
border: 3px solid #eee;
|
|
3304
|
-
border-top-color: #007bff;
|
|
3305
|
-
border-radius: 50%;
|
|
3306
|
-
animation: spin 0.8s linear infinite;
|
|
3553
|
+
.input-button.recording {
|
|
3554
|
+
background: #e74c3c;
|
|
3555
|
+
animation: recordPulse 1.5s infinite;
|
|
3307
3556
|
}
|
|
3308
3557
|
|
|
3309
|
-
@keyframes
|
|
3310
|
-
|
|
3558
|
+
@keyframes recordPulse {
|
|
3559
|
+
0%, 100% { transform: scale(1); }
|
|
3560
|
+
50% { transform: scale(1.1); }
|
|
3311
3561
|
}
|
|
3312
3562
|
|
|
3313
3563
|
/* Accessibility */
|
|
@@ -3320,61 +3570,89 @@ const WIDGET_STYLES = `
|
|
|
3320
3570
|
overflow: hidden;
|
|
3321
3571
|
clip: rect(0, 0, 0, 0);
|
|
3322
3572
|
white-space: nowrap;
|
|
3323
|
-
border: 0;
|
|
3573
|
+
border-width: 0;
|
|
3324
3574
|
}
|
|
3325
3575
|
`;
|
|
3326
3576
|
const WIDGET_TEMPLATE = `
|
|
3327
3577
|
<div class="widget-root">
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
</div>
|
|
3578
|
+
<!-- Avatar Circle - Breaking out at top -->
|
|
3579
|
+
<div class="avatar-circle" id="avatarCircle" aria-label="AI Avatar">
|
|
3580
|
+
<div class="avatar-placeholder">👤</div>
|
|
3332
3581
|
</div>
|
|
3333
|
-
|
|
3334
|
-
<div class="chat-header">
|
|
3335
|
-
<
|
|
3336
|
-
|
|
3337
|
-
<
|
|
3338
|
-
</div>
|
|
3339
|
-
<div class="header-actions">
|
|
3340
|
-
<button class="icon-btn minimize-btn" aria-label="Minimize">
|
|
3341
|
-
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
3342
|
-
<line x1="5" y1="12" x2="19" y2="12"/>
|
|
3343
|
-
</svg>
|
|
3344
|
-
</button>
|
|
3582
|
+
|
|
3583
|
+
<div class="chat-header" role="button" tabindex="0" aria-label="Toggle chat window">
|
|
3584
|
+
<h3>Nyx Assistant</h3>
|
|
3585
|
+
<div class="chat-header-buttons">
|
|
3586
|
+
<button id="minimizeBtn" aria-label="Minimize chat" title="Minimize">−</button>
|
|
3345
3587
|
</div>
|
|
3346
3588
|
</div>
|
|
3347
|
-
|
|
3348
|
-
<div
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
class="chat-input"
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3589
|
+
|
|
3590
|
+
<div class="chat-messages" id="chatMessages" role="log" aria-live="polite" aria-atomic="false">
|
|
3591
|
+
<!-- Messages will be added here dynamically -->
|
|
3592
|
+
</div>
|
|
3593
|
+
|
|
3594
|
+
<div class="chat-input-area">
|
|
3595
|
+
<div class="chat-input-wrapper">
|
|
3596
|
+
<div class="chat-input-controls">
|
|
3597
|
+
<input
|
|
3598
|
+
type="text"
|
|
3599
|
+
id="chatInput"
|
|
3600
|
+
placeholder="Type a message..."
|
|
3601
|
+
aria-label="Chat message input"
|
|
3602
|
+
autocomplete="off"
|
|
3603
|
+
/>
|
|
3604
|
+
<button
|
|
3605
|
+
id="micBtn"
|
|
3606
|
+
class="input-button"
|
|
3607
|
+
aria-label="Voice input"
|
|
3608
|
+
title="Voice input"
|
|
3609
|
+
>
|
|
3610
|
+
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
3611
|
+
<path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"/>
|
|
3612
|
+
<path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
|
|
3613
|
+
<line x1="12" x2="12" y1="19" y2="22"/>
|
|
3614
|
+
</svg>
|
|
3615
|
+
</button>
|
|
3616
|
+
<button
|
|
3617
|
+
id="sendBtn"
|
|
3618
|
+
class="input-button"
|
|
3619
|
+
aria-label="Send message"
|
|
3620
|
+
title="Send"
|
|
3621
|
+
>
|
|
3622
|
+
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
3623
|
+
<path d="M22 2 11 13"/>
|
|
3624
|
+
<path d="M22 2 15 22 11 13 2 9 22 2z"/>
|
|
3625
|
+
</svg>
|
|
3626
|
+
</button>
|
|
3627
|
+
</div>
|
|
3628
|
+
</div>
|
|
3368
3629
|
</div>
|
|
3369
3630
|
</div>
|
|
3370
3631
|
`;
|
|
3371
3632
|
const BUBBLE_TEMPLATE = `
|
|
3372
3633
|
<div class="chat-bubble" role="button" aria-label="Open chat" tabindex="0">
|
|
3373
|
-
<svg viewBox="0 0 24 24">
|
|
3374
|
-
<path d="
|
|
3634
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
3635
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
|
|
3375
3636
|
</svg>
|
|
3376
3637
|
</div>
|
|
3377
3638
|
`;
|
|
3639
|
+
const DEFAULT_CONFIG$1 = {
|
|
3640
|
+
position: "bottom-right",
|
|
3641
|
+
theme: "light",
|
|
3642
|
+
startCollapsed: true,
|
|
3643
|
+
width: 380,
|
|
3644
|
+
height: 550,
|
|
3645
|
+
enableVoice: true,
|
|
3646
|
+
enableText: true,
|
|
3647
|
+
authEnabled: false,
|
|
3648
|
+
logLevel: "error"
|
|
3649
|
+
};
|
|
3650
|
+
const log$2 = logger.scope("Widget");
|
|
3651
|
+
const DEFAULT_CONFIG = {
|
|
3652
|
+
...DEFAULT_CONFIG$1,
|
|
3653
|
+
avatarUrl: "./asset/nyx.zip"
|
|
3654
|
+
// Override for local dev compatibility
|
|
3655
|
+
};
|
|
3378
3656
|
class AvatarChatElement extends HTMLElement {
|
|
3379
3657
|
constructor() {
|
|
3380
3658
|
super();
|
|
@@ -3383,6 +3661,8 @@ class AvatarChatElement extends HTMLElement {
|
|
|
3383
3661
|
this._isMounted = false;
|
|
3384
3662
|
this._isConnected = false;
|
|
3385
3663
|
this._isCollapsed = false;
|
|
3664
|
+
this.themeMediaQuery = null;
|
|
3665
|
+
this.themeChangeHandler = null;
|
|
3386
3666
|
this.shadow = this.attachShadow({ mode: "open" });
|
|
3387
3667
|
}
|
|
3388
3668
|
/**
|
|
@@ -3418,7 +3698,7 @@ class AvatarChatElement extends HTMLElement {
|
|
|
3418
3698
|
this.classList.add(`position-${this.config.position}`);
|
|
3419
3699
|
}
|
|
3420
3700
|
this.style.width = `${this.config.width}px`;
|
|
3421
|
-
this.style.
|
|
3701
|
+
this.style.maxHeight = `${this.config.height}px`;
|
|
3422
3702
|
if (this.config.startCollapsed) {
|
|
3423
3703
|
this._isCollapsed = true;
|
|
3424
3704
|
this.classList.add("collapsed");
|
|
@@ -3453,7 +3733,7 @@ class AvatarChatElement extends HTMLElement {
|
|
|
3453
3733
|
if (voiceBtn) voiceBtn.style.display = "none";
|
|
3454
3734
|
}
|
|
3455
3735
|
if (!this.config.enableText) {
|
|
3456
|
-
const inputSection = this.shadow.querySelector(".input-
|
|
3736
|
+
const inputSection = this.shadow.querySelector(".chat-input-area");
|
|
3457
3737
|
if (inputSection) inputSection.style.display = "none";
|
|
3458
3738
|
}
|
|
3459
3739
|
await this.initializeAvatar();
|
|
@@ -3485,8 +3765,7 @@ class AvatarChatElement extends HTMLElement {
|
|
|
3485
3765
|
return;
|
|
3486
3766
|
}
|
|
3487
3767
|
const renderContainer = document.createElement("div");
|
|
3488
|
-
renderContainer.
|
|
3489
|
-
renderContainer.style.height = "400px";
|
|
3768
|
+
renderContainer.className = "avatar-render-container";
|
|
3490
3769
|
avatarContainer.appendChild(renderContainer);
|
|
3491
3770
|
try {
|
|
3492
3771
|
this.avatar = new LazyAvatar(
|
|
@@ -3553,44 +3832,36 @@ class AvatarChatElement extends HTMLElement {
|
|
|
3553
3832
|
* Setup UI event listeners
|
|
3554
3833
|
*/
|
|
3555
3834
|
setupUIEvents() {
|
|
3556
|
-
const minimizeBtn = this.shadow.
|
|
3835
|
+
const minimizeBtn = this.shadow.getElementById("minimizeBtn");
|
|
3557
3836
|
minimizeBtn?.addEventListener("click", () => this.collapse());
|
|
3558
3837
|
if (this.config.theme === "auto") {
|
|
3559
|
-
window.matchMedia("(prefers-color-scheme: dark)")
|
|
3838
|
+
this.themeMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
3839
|
+
this.themeChangeHandler = (e) => {
|
|
3560
3840
|
const root = this.shadow.querySelector(".widget-root");
|
|
3561
3841
|
root?.classList.toggle("theme-dark", e.matches);
|
|
3562
|
-
}
|
|
3842
|
+
};
|
|
3843
|
+
this.themeMediaQuery.addEventListener("change", this.themeChangeHandler);
|
|
3563
3844
|
}
|
|
3564
3845
|
}
|
|
3565
3846
|
/**
|
|
3566
|
-
* Update connection status UI
|
|
3847
|
+
* Update connection status (stub - connection indicator removed from UI)
|
|
3567
3848
|
*/
|
|
3568
|
-
updateConnectionStatus(
|
|
3569
|
-
const dot = this.shadow.querySelector(".status-dot");
|
|
3570
|
-
const text = this.shadow.querySelector(".status-text");
|
|
3571
|
-
if (dot) {
|
|
3572
|
-
dot.classList.remove("connected", "connecting", "error");
|
|
3573
|
-
if (state === "error") {
|
|
3574
|
-
dot.classList.add("error");
|
|
3575
|
-
} else {
|
|
3576
|
-
dot.classList.add(connected ? "connected" : "connecting");
|
|
3577
|
-
}
|
|
3578
|
-
}
|
|
3579
|
-
if (text) {
|
|
3580
|
-
if (state === "error") {
|
|
3581
|
-
text.textContent = "Connection failed";
|
|
3582
|
-
} else {
|
|
3583
|
-
text.textContent = connected ? "Connected" : "Connecting...";
|
|
3584
|
-
}
|
|
3585
|
-
}
|
|
3849
|
+
updateConnectionStatus(_connected, _state) {
|
|
3586
3850
|
}
|
|
3587
3851
|
/**
|
|
3588
3852
|
* Collapse to bubble
|
|
3589
3853
|
*/
|
|
3590
3854
|
collapse() {
|
|
3591
3855
|
if (this._isCollapsed) return;
|
|
3856
|
+
if (this.chatManager) {
|
|
3857
|
+
this.chatManager.resetOnMinimize();
|
|
3858
|
+
}
|
|
3592
3859
|
this._isCollapsed = true;
|
|
3593
3860
|
this.classList.add("collapsed");
|
|
3861
|
+
const widgetRoot = this.shadow.querySelector(".widget-root");
|
|
3862
|
+
if (widgetRoot) {
|
|
3863
|
+
widgetRoot.style.display = "none";
|
|
3864
|
+
}
|
|
3594
3865
|
this.renderBubble();
|
|
3595
3866
|
}
|
|
3596
3867
|
/**
|
|
@@ -3601,8 +3872,18 @@ class AvatarChatElement extends HTMLElement {
|
|
|
3601
3872
|
this._isCollapsed = false;
|
|
3602
3873
|
this.classList.remove("collapsed");
|
|
3603
3874
|
this.style.width = `${this.config.width}px`;
|
|
3604
|
-
this.style.
|
|
3605
|
-
|
|
3875
|
+
this.style.maxHeight = `${this.config.height}px`;
|
|
3876
|
+
const widgetRoot = this.shadow.querySelector(".widget-root");
|
|
3877
|
+
if (widgetRoot) {
|
|
3878
|
+
const bubble = this.shadow.querySelector(".chat-bubble");
|
|
3879
|
+
if (bubble) bubble.remove();
|
|
3880
|
+
widgetRoot.style.display = "flex";
|
|
3881
|
+
if (this.chatManager) {
|
|
3882
|
+
await this.chatManager.reconnectOnExpand();
|
|
3883
|
+
}
|
|
3884
|
+
} else {
|
|
3885
|
+
await this.renderWidget();
|
|
3886
|
+
}
|
|
3606
3887
|
}
|
|
3607
3888
|
/**
|
|
3608
3889
|
* Show widget
|
|
@@ -3636,11 +3917,26 @@ class AvatarChatElement extends HTMLElement {
|
|
|
3636
3917
|
isServerConnected() {
|
|
3637
3918
|
return this._isConnected;
|
|
3638
3919
|
}
|
|
3920
|
+
/**
|
|
3921
|
+
* Manually reconnect to the server
|
|
3922
|
+
* Useful after network changes or connection failures
|
|
3923
|
+
*/
|
|
3924
|
+
async reconnect() {
|
|
3925
|
+
if (!this.chatManager) {
|
|
3926
|
+
throw new Error("Widget not initialized");
|
|
3927
|
+
}
|
|
3928
|
+
return this.chatManager.reconnect();
|
|
3929
|
+
}
|
|
3639
3930
|
/**
|
|
3640
3931
|
* Cleanup
|
|
3641
3932
|
*/
|
|
3642
3933
|
destroy() {
|
|
3643
3934
|
log$2.info("Destroying widget");
|
|
3935
|
+
if (this.themeMediaQuery && this.themeChangeHandler) {
|
|
3936
|
+
this.themeMediaQuery.removeEventListener("change", this.themeChangeHandler);
|
|
3937
|
+
this.themeMediaQuery = null;
|
|
3938
|
+
this.themeChangeHandler = null;
|
|
3939
|
+
}
|
|
3644
3940
|
if (this.chatManager) {
|
|
3645
3941
|
this.chatManager.dispose();
|
|
3646
3942
|
this.chatManager = null;
|
|
@@ -3663,6 +3959,25 @@ const AvatarChat = {
|
|
|
3663
3959
|
version: "__VERSION__",
|
|
3664
3960
|
/** Active instance */
|
|
3665
3961
|
_instance: null,
|
|
3962
|
+
/**
|
|
3963
|
+
* Get the URL for the default included avatar
|
|
3964
|
+
* Auto-detects CDN usage and returns the appropriate URL
|
|
3965
|
+
*/
|
|
3966
|
+
getDefaultAvatarUrl() {
|
|
3967
|
+
const scripts = document.getElementsByTagName("script");
|
|
3968
|
+
for (let i = 0; i < scripts.length; i++) {
|
|
3969
|
+
const src = scripts[i].src;
|
|
3970
|
+
if (src.includes("jsdelivr.net") && src.includes("avatar-chat-widget")) {
|
|
3971
|
+
const baseUrl = src.substring(0, src.lastIndexOf("/"));
|
|
3972
|
+
return `${baseUrl}/avatar-chat-widget/public/asset/nyx.zip`;
|
|
3973
|
+
}
|
|
3974
|
+
if (src.includes("unpkg.com") && src.includes("avatar-chat-widget")) {
|
|
3975
|
+
const baseUrl = src.substring(0, src.lastIndexOf("/"));
|
|
3976
|
+
return `${baseUrl}/avatar-chat-widget/public/asset/nyx.zip`;
|
|
3977
|
+
}
|
|
3978
|
+
}
|
|
3979
|
+
return "/asset/nyx.zip";
|
|
3980
|
+
},
|
|
3666
3981
|
/**
|
|
3667
3982
|
* Initialize and mount the widget
|
|
3668
3983
|
*/
|
|
@@ -3670,9 +3985,74 @@ const AvatarChat = {
|
|
|
3670
3985
|
if (!config.serverUrl) {
|
|
3671
3986
|
throw new Error("AvatarChat.init(): serverUrl is required");
|
|
3672
3987
|
}
|
|
3988
|
+
if (!config.serverUrl.match(/^wss?:\/\/.+/)) {
|
|
3989
|
+
throw new Error("AvatarChat.init(): serverUrl must be a valid WebSocket URL (ws:// or wss://)");
|
|
3990
|
+
}
|
|
3673
3991
|
if (!config.container) {
|
|
3674
3992
|
throw new Error("AvatarChat.init(): container is required");
|
|
3675
3993
|
}
|
|
3994
|
+
const containerElement = typeof config.container === "string" ? document.querySelector(config.container) : config.container;
|
|
3995
|
+
if (!containerElement) {
|
|
3996
|
+
throw new Error(`AvatarChat.init(): container not found: ${config.container}`);
|
|
3997
|
+
}
|
|
3998
|
+
if (config.width !== void 0) {
|
|
3999
|
+
if (typeof config.width !== "number" || config.width < 200 || config.width > 2e3) {
|
|
4000
|
+
throw new Error("AvatarChat.init(): width must be a number between 200 and 2000 pixels");
|
|
4001
|
+
}
|
|
4002
|
+
}
|
|
4003
|
+
if (config.height !== void 0) {
|
|
4004
|
+
if (typeof config.height !== "number" || config.height < 300 || config.height > 2e3) {
|
|
4005
|
+
throw new Error("AvatarChat.init(): height must be a number between 300 and 2000 pixels");
|
|
4006
|
+
}
|
|
4007
|
+
}
|
|
4008
|
+
if (config.onReady !== void 0 && typeof config.onReady !== "function") {
|
|
4009
|
+
throw new Error("AvatarChat.init(): onReady must be a function");
|
|
4010
|
+
}
|
|
4011
|
+
if (config.onMessage !== void 0 && typeof config.onMessage !== "function") {
|
|
4012
|
+
throw new Error("AvatarChat.init(): onMessage must be a function");
|
|
4013
|
+
}
|
|
4014
|
+
if (config.onError !== void 0 && typeof config.onError !== "function") {
|
|
4015
|
+
throw new Error("AvatarChat.init(): onError must be a function");
|
|
4016
|
+
}
|
|
4017
|
+
if (config.logLevel !== void 0) {
|
|
4018
|
+
const validLogLevels = ["none", "error", "warn", "info", "debug"];
|
|
4019
|
+
if (!validLogLevels.includes(config.logLevel)) {
|
|
4020
|
+
throw new Error(`AvatarChat.init(): logLevel must be one of: ${validLogLevels.join(", ")}`);
|
|
4021
|
+
}
|
|
4022
|
+
}
|
|
4023
|
+
if (config.theme !== void 0) {
|
|
4024
|
+
const validThemes = ["light", "dark", "auto"];
|
|
4025
|
+
if (!validThemes.includes(config.theme)) {
|
|
4026
|
+
throw new Error(`AvatarChat.init(): theme must be one of: ${validThemes.join(", ")}`);
|
|
4027
|
+
}
|
|
4028
|
+
}
|
|
4029
|
+
if (config.position !== void 0) {
|
|
4030
|
+
const validPositions = ["inline", "bottom-right", "bottom-left", "top-right", "top-left"];
|
|
4031
|
+
if (!validPositions.includes(config.position)) {
|
|
4032
|
+
throw new Error(`AvatarChat.init(): position must be one of: ${validPositions.join(", ")}`);
|
|
4033
|
+
}
|
|
4034
|
+
}
|
|
4035
|
+
if (config.assetsBaseUrl) {
|
|
4036
|
+
setConfig({ assets: { baseUrl: config.assetsBaseUrl, defaultAvatarPath: "/asset/nyx.zip" } });
|
|
4037
|
+
} else {
|
|
4038
|
+
const scripts = document.getElementsByTagName("script");
|
|
4039
|
+
for (let i = 0; i < scripts.length; i++) {
|
|
4040
|
+
const src = scripts[i].src;
|
|
4041
|
+
if (src.includes("jsdelivr.net") && src.includes("avatar-chat-widget")) {
|
|
4042
|
+
const baseUrl = src.substring(0, src.lastIndexOf("/"));
|
|
4043
|
+
setConfig({ assets: { baseUrl: `${baseUrl}/public`, defaultAvatarPath: "/asset/nyx.zip" } });
|
|
4044
|
+
break;
|
|
4045
|
+
}
|
|
4046
|
+
if (src.includes("unpkg.com") && src.includes("avatar-chat-widget")) {
|
|
4047
|
+
const baseUrl = src.substring(0, src.lastIndexOf("/"));
|
|
4048
|
+
setConfig({ assets: { baseUrl: `${baseUrl}/public`, defaultAvatarPath: "/asset/nyx.zip" } });
|
|
4049
|
+
break;
|
|
4050
|
+
}
|
|
4051
|
+
}
|
|
4052
|
+
}
|
|
4053
|
+
if (!config.avatarUrl) {
|
|
4054
|
+
config.avatarUrl = this.getDefaultAvatarUrl();
|
|
4055
|
+
}
|
|
3676
4056
|
const containerEl = typeof config.container === "string" ? document.querySelector(config.container) : config.container;
|
|
3677
4057
|
if (!containerEl) {
|
|
3678
4058
|
throw new Error(`AvatarChat.init(): container not found: ${config.container}`);
|
|
@@ -3698,7 +4078,8 @@ const AvatarChat = {
|
|
|
3698
4078
|
expand: () => widget.expand(),
|
|
3699
4079
|
collapse: () => widget.collapse(),
|
|
3700
4080
|
isMounted: () => widget.isMounted(),
|
|
3701
|
-
isConnected: () => widget.isServerConnected()
|
|
4081
|
+
isConnected: () => widget.isServerConnected(),
|
|
4082
|
+
reconnect: () => widget.reconnect()
|
|
3702
4083
|
};
|
|
3703
4084
|
},
|
|
3704
4085
|
/**
|
|
@@ -3728,7 +4109,8 @@ const AvatarChat = {
|
|
|
3728
4109
|
expand: () => widget.expand(),
|
|
3729
4110
|
collapse: () => widget.collapse(),
|
|
3730
4111
|
isMounted: () => widget.isMounted(),
|
|
3731
|
-
isConnected: () => widget.isServerConnected()
|
|
4112
|
+
isConnected: () => widget.isServerConnected(),
|
|
4113
|
+
reconnect: () => widget.reconnect()
|
|
3732
4114
|
};
|
|
3733
4115
|
}
|
|
3734
4116
|
};
|
|
@@ -40123,7 +40505,7 @@ function interceptControlUp(event) {
|
|
|
40123
40505
|
document2.removeEventListener("keyup", this._interceptControlUp, { passive: true, capture: true });
|
|
40124
40506
|
}
|
|
40125
40507
|
}
|
|
40126
|
-
const j = { None: 0, Info: 3 }, Y = { Always: 0, Never: 2 }, J = { Ply: 0 }, X = (e) => e.endsWith(".ply") ? J.Ply : null, $ = { Default: 0, Instant: 2 }, Z = { ThreeD: 0, TwoD: 1 };
|
|
40508
|
+
const j = { None: 0, Info: 3 }, Y = { Always: 0, Never: 2 }, J = { Ply: 0 }, X = (e) => e.endsWith(".ply") ? J.Ply : null, $ = { Default: 0, Gradual: 1, Instant: 2 }, Z = { ThreeD: 0, TwoD: 1 };
|
|
40127
40509
|
let ee = (_a = class {
|
|
40128
40510
|
}, __publicField(_a, "DefaultSplatSortDistanceMapPrecision", 16), __publicField(_a, "MemoryPageSize", 65536), __publicField(_a, "BytesPerFloat", 4), __publicField(_a, "BytesPerInt", 4), __publicField(_a, "MaxScenes", 32), __publicField(_a, "ProgressiveLoadSectionSize", 262144), __publicField(_a, "ProgressiveLoadSectionDelayDuration", 15), __publicField(_a, "SphericalHarmonics8BitCompressionRange", 3), _a);
|
|
40129
40511
|
const te = ee.SphericalHarmonics8BitCompressionRange, Ae = 15e5, Ce = { DirectToSplatBuffer: 0, DirectToSplatArray: 1, DownloadBeforeProcessing: 2 }, ye = { Downloading: 0, Processing: 1, Done: 2 };
|
|
@@ -41121,7 +41503,7 @@ class As {
|
|
|
41121
41503
|
` : t += "\n bool isRightIris = false;\n ", e.left_iris && e.left_iris.length > 0 ? t += `
|
|
41122
41504
|
// Check if this splat is part of left iris
|
|
41123
41505
|
bool isLeftIris = ${e.left_iris.map(([e2, t2]) => `(idx >= ${e2}.0 && idx <= ${t2}.0)`).join(" ||\n ")};
|
|
41124
|
-
` : t += "\n bool isLeftIris = false;\n ", t += "\n float finalOpacity = opacity;\n\n //
|
|
41506
|
+
` : t += "\n bool isLeftIris = false;\n ", t += "\n float finalOpacity = opacity;\n\n // Very narrow fade window at high blink values only\n // Iris stays visible until eye is almost completely closed\n if (isRightIris) {\n float fadeFactor = 1.0 - smoothstep(0.5, 0.7, eyeBlinkRight);\n finalOpacity = opacity * fadeFactor;\n } else if (isLeftIris) {\n float fadeFactor = 1.0 - smoothstep(0.5, 0.7, eyeBlinkLeft);\n finalOpacity = opacity * fadeFactor;\n }\n\n if (finalOpacity < 1.0 / 255.0)\n discard;\n\n gl_FragColor = vec4(vColor.rgb, finalOpacity);\n }\n ") : t += "\n gl_FragColor = vec4(vColor.rgb, opacity);\n }\n ", t;
|
|
41125
41507
|
}
|
|
41126
41508
|
}
|
|
41127
41509
|
class fs {
|
|
@@ -41249,7 +41631,7 @@ function Is(e, t, s) {
|
|
|
41249
41631
|
}
|
|
41250
41632
|
const bs = 16777216;
|
|
41251
41633
|
class ws extends Mesh {
|
|
41252
|
-
constructor(e = Z.ThreeD, t = false, i = false, n = false, r = 1, a = true, o = false, l = false, c = 1024, h = j.None, d = 0, u = 1, p = 0.3) {
|
|
41634
|
+
constructor(e = Z.ThreeD, t = false, i = false, n = false, r = 1, a = true, o = false, l = false, c = 1024, h = j.None, d = 0, u = 1, p = 0.3, m = null) {
|
|
41253
41635
|
super(Cs, ys);
|
|
41254
41636
|
__publicField(this, "buildSplatTree", (e = [], t, s) => new Promise((i) => {
|
|
41255
41637
|
this.disposeSplatTree(), this.baseSplatTree = new ms(8, 1e3);
|
|
@@ -41405,7 +41787,7 @@ class ws extends Mesh {
|
|
|
41405
41787
|
this.getLocalSplatParameters(t, e), e.splatBuffer.getSplatColor(e.localIndex, s);
|
|
41406
41788
|
};
|
|
41407
41789
|
}());
|
|
41408
|
-
this.renderer = void 0, this.splatRenderMode = e, this.dynamicMode = t, this.enableOptionalEffects = i, this.halfPrecisionCovariancesOnGPU = n, this.devicePixelRatio = r, this.enableDistancesComputationOnGPU = a, this.integerBasedDistancesComputation = o, this.antialiased = l, this.kernel2DSize = p, this.maxScreenSpaceSplatSize = c, this.logLevel = h, this.sphericalHarmonicsDegree = d, this.minSphericalHarmonicsDegree = 0, this.sceneFadeInRateMultiplier = u, this.scenes = [], this.splatTree = null, this.baseSplatTree = null, this.splatDataTextures = {}, this.flameModel = null, this.expressionBSNum = 0, this.bsWeight = [], this.bonesMatrix = null, this.bonesNum = null, this.bonesWeight = null, this.gaussianSplatCount = null, this.morphTargetDictionary = null, this.distancesTransformFeedback = { id: null, vertexShader: null, fragmentShader: null, program: null, centersBuffer: null, sceneIndexesBuffer: null, outDistancesBuffer: null, centersLoc: -1, modelViewProjLoc: -1, sceneIndexesLoc: -1, transformsLocs: [] }, this.globalSplatIndexToLocalSplatIndexMap = [], this.globalSplatIndexToSceneIndexMap = [], this.irisOcclusionConfig =
|
|
41790
|
+
this.renderer = void 0, this.splatRenderMode = e, this.dynamicMode = t, this.enableOptionalEffects = i, this.halfPrecisionCovariancesOnGPU = n, this.devicePixelRatio = r, this.enableDistancesComputationOnGPU = a, this.integerBasedDistancesComputation = o, this.antialiased = l, this.kernel2DSize = p, this.maxScreenSpaceSplatSize = c, this.logLevel = h, this.sphericalHarmonicsDegree = d, this.minSphericalHarmonicsDegree = 0, this.sceneFadeInRateMultiplier = u, this.scenes = [], this.splatTree = null, this.baseSplatTree = null, this.splatDataTextures = {}, this.flameModel = null, this.expressionBSNum = 0, this.bsWeight = [], this.bonesMatrix = null, this.bonesNum = null, this.bonesWeight = null, this.gaussianSplatCount = null, this.morphTargetDictionary = null, this.distancesTransformFeedback = { id: null, vertexShader: null, fragmentShader: null, program: null, centersBuffer: null, sceneIndexesBuffer: null, outDistancesBuffer: null, centersLoc: -1, modelViewProjLoc: -1, sceneIndexesLoc: -1, transformsLocs: [] }, this.globalSplatIndexToLocalSplatIndexMap = [], this.globalSplatIndexToSceneIndexMap = [], this.irisOcclusionConfig = m, this.lastBuildSplatCount = 0, this.lastBuildScenes = [], this.lastBuildMaxSplatCount = 0, this.lastBuildSceneCount = 0, this.firstRenderTime = -1, this.finalBuild = false, this.webGLUtils = null, this.boundingBox = new Box3(), this.calculatedSceneCenter = new Vector3(), this.maxSplatDistanceFromSceneCenter = 0, this.visibleRegionBufferRadius = 0, this.visibleRegionRadius = 0, this.visibleRegionFadeStartRadius = 0, this.visibleRegionChanging = false, this.splatScale = 1, this.pointCloudModeEnabled = false, this.disposed = false, this.lastRenderer = null, this.visible = false;
|
|
41409
41791
|
}
|
|
41410
41792
|
static buildScenes(e, t, i) {
|
|
41411
41793
|
const r = [];
|
|
@@ -43256,7 +43638,7 @@ const _Viewer = class _Viewer {
|
|
|
43256
43638
|
}
|
|
43257
43639
|
};
|
|
43258
43640
|
}());
|
|
43259
|
-
if (e.cameraUp || (e.cameraUp = [0, 1, 0]), this.cameraUp = new Vector3().fromArray(e.cameraUp), e.initialCameraPosition || (e.initialCameraPosition = [0, 10, 15]), this.initialCameraPosition = new Vector3().fromArray(e.initialCameraPosition), e.initialCameraRotation || (e.initialCameraRotation = [0, 0, 0]), this.initialCameraRotation = new Vector3().fromArray(e.initialCameraRotation), this.backgroundColor = e.backgroundColor, e.initialCameraLookAt || (e.initialCameraLookAt = [0, 0, 0]), this.initialCameraLookAt = new Vector3().fromArray(e.initialCameraLookAt), this.dropInMode = e.dropInMode || false, void 0 !== e.selfDrivenMode && null !== e.selfDrivenMode || (e.selfDrivenMode = true), this.selfDrivenMode = e.selfDrivenMode && !this.dropInMode, this.selfDrivenUpdateFunc = this.selfDrivenUpdate.bind(this), void 0 === e.useBuiltInControls && (e.useBuiltInControls = true), this.useBuiltInControls = e.useBuiltInControls, this.rootElement = e.rootElement, this.canvas = e.threejsCanvas, this.ignoreDevicePixelRatio = e.ignoreDevicePixelRatio || false, this.devicePixelRatio = this.ignoreDevicePixelRatio ? 1 : window.devicePixelRatio || 1, this.halfPrecisionCovariancesOnGPU = e.halfPrecisionCovariancesOnGPU || false, this.threeScene = e.threeScene, this.renderer = e.renderer, this.camera = e.camera, this.gpuAcceleratedSort = e.gpuAcceleratedSort || false, void 0 !== e.integerBasedSort && null !== e.integerBasedSort || (e.integerBasedSort = true), this.integerBasedSort = e.integerBasedSort, void 0 !== e.sharedMemoryForWorkers && null !== e.sharedMemoryForWorkers || (e.sharedMemoryForWorkers = true), this.sharedMemoryForWorkers = false, this.dynamicScene = !!e.dynamicScene, this.antialiased = e.antialiased || false, this.kernel2DSize = void 0 === e.kernel2DSize ? 0.3 : e.kernel2DSize, this.renderMode = e.renderMode || Y.Always, this.sceneRevealMode = e.sceneRevealMode || $.Default, this.focalAdjustment = e.focalAdjustment || 1, this.maxScreenSpaceSplatSize = e.maxScreenSpaceSplatSize || 1024, this.logLevel = e.logLevel || j.None, this.sphericalHarmonicsDegree = e.sphericalHarmonicsDegree || 0, this.enableOptionalEffects = e.enableOptionalEffects || false, void 0 !== e.enableSIMDInSort && null !== e.enableSIMDInSort || (e.enableSIMDInSort = true), this.enableSIMDInSort = e.enableSIMDInSort, void 0 !== e.inMemoryCompressionLevel && null !== e.inMemoryCompressionLevel || (e.inMemoryCompressionLevel = 0), this.inMemoryCompressionLevel = e.inMemoryCompressionLevel, void 0 !== e.optimizeSplatData && null !== e.optimizeSplatData || (e.optimizeSplatData = true), this.optimizeSplatData = e.optimizeSplatData, void 0 !== e.freeIntermediateSplatData && null !== e.freeIntermediateSplatData || (e.freeIntermediateSplatData = false), this.freeIntermediateSplatData = e.freeIntermediateSplatData, je()) {
|
|
43641
|
+
if (e.cameraUp || (e.cameraUp = [0, 1, 0]), this.cameraUp = new Vector3().fromArray(e.cameraUp), e.initialCameraPosition || (e.initialCameraPosition = [0, 10, 15]), this.initialCameraPosition = new Vector3().fromArray(e.initialCameraPosition), e.initialCameraRotation || (e.initialCameraRotation = [0, 0, 0]), this.initialCameraRotation = new Vector3().fromArray(e.initialCameraRotation), this.backgroundColor = e.backgroundColor, this.irisOcclusionConfig = e.irisOcclusionConfig || null, e.initialCameraLookAt || (e.initialCameraLookAt = [0, 0, 0]), this.initialCameraLookAt = new Vector3().fromArray(e.initialCameraLookAt), this.dropInMode = e.dropInMode || false, void 0 !== e.selfDrivenMode && null !== e.selfDrivenMode || (e.selfDrivenMode = true), this.selfDrivenMode = e.selfDrivenMode && !this.dropInMode, this.selfDrivenUpdateFunc = this.selfDrivenUpdate.bind(this), void 0 === e.useBuiltInControls && (e.useBuiltInControls = true), this.useBuiltInControls = e.useBuiltInControls, this.rootElement = e.rootElement, this.canvas = e.threejsCanvas, this.ignoreDevicePixelRatio = e.ignoreDevicePixelRatio || false, this.devicePixelRatio = this.ignoreDevicePixelRatio ? 1 : window.devicePixelRatio || 1, this.halfPrecisionCovariancesOnGPU = e.halfPrecisionCovariancesOnGPU || false, this.threeScene = e.threeScene, this.renderer = e.renderer, this.camera = e.camera, this.gpuAcceleratedSort = e.gpuAcceleratedSort || false, void 0 !== e.integerBasedSort && null !== e.integerBasedSort || (e.integerBasedSort = true), this.integerBasedSort = e.integerBasedSort, void 0 !== e.sharedMemoryForWorkers && null !== e.sharedMemoryForWorkers || (e.sharedMemoryForWorkers = true), this.sharedMemoryForWorkers = false, this.dynamicScene = !!e.dynamicScene, this.antialiased = e.antialiased || false, this.kernel2DSize = void 0 === e.kernel2DSize ? 0.3 : e.kernel2DSize, this.renderMode = e.renderMode || Y.Always, this.sceneRevealMode = e.sceneRevealMode || $.Default, this.focalAdjustment = e.focalAdjustment || 1, this.maxScreenSpaceSplatSize = e.maxScreenSpaceSplatSize || 1024, this.logLevel = e.logLevel || j.None, this.sphericalHarmonicsDegree = e.sphericalHarmonicsDegree || 0, this.enableOptionalEffects = e.enableOptionalEffects || false, void 0 !== e.enableSIMDInSort && null !== e.enableSIMDInSort || (e.enableSIMDInSort = true), this.enableSIMDInSort = e.enableSIMDInSort, void 0 !== e.inMemoryCompressionLevel && null !== e.inMemoryCompressionLevel || (e.inMemoryCompressionLevel = 0), this.inMemoryCompressionLevel = e.inMemoryCompressionLevel, void 0 !== e.optimizeSplatData && null !== e.optimizeSplatData || (e.optimizeSplatData = true), this.optimizeSplatData = e.optimizeSplatData, void 0 !== e.freeIntermediateSplatData && null !== e.freeIntermediateSplatData || (e.freeIntermediateSplatData = false), this.freeIntermediateSplatData = e.freeIntermediateSplatData, je()) {
|
|
43260
43642
|
const e2 = Ye();
|
|
43261
43643
|
e2.major < 17 && (this.enableSIMDInSort = false), e2.major < 16 && (this.sharedMemoryForWorkers = false);
|
|
43262
43644
|
}
|
|
@@ -43265,7 +43647,7 @@ const _Viewer = class _Viewer {
|
|
|
43265
43647
|
this.splatSortDistanceMapPrecision = ze(this.splatSortDistanceMapPrecision, 10, t), this.onSplatMeshChangedCallback = null, this.createSplatMesh(), this.controls = null, this.perspectiveControls = null, this.orthographicControls = null, this.orthographicCamera = null, this.perspectiveCamera = null, this.showMeshCursor = false, this.showControlPlane = false, this.showInfo = false, this.sceneHelper = null, this.sortWorker = null, this.sortRunning = false, this.splatRenderCount = 0, this.splatSortCount = 0, this.lastSplatSortCount = 0, this.sortWorkerIndexesToSort = null, this.sortWorkerSortedIndexes = null, this.sortWorkerPrecomputedDistances = null, this.sortWorkerTransforms = null, this.preSortMessages = [], this.runAfterNextSort = [], this.selfDrivenModeRunning = false, this.splatRenderReady = false, this.raycaster = new wi(), this.infoPanel = null, this.startInOrthographicMode = false, this.currentFPS = 0, this.lastSortTime = 0, this.consecutiveRenderFrames = 0, this.previousCameraTarget = new Vector3(), this.nextCameraTarget = new Vector3(), this.mousePosition = new Vector2(), this.mouseDownPosition = new Vector2(), this.mouseDownTime = null, this.resizeObserver = null, this.mouseMoveListener = null, this.mouseDownListener = null, this.mouseUpListener = null, this.keyDownListener = null, this.sortPromise = null, this.sortPromiseResolver = null, this.splatSceneDownloadControllers = [], this.splatSceneDownloadPromises = {}, this.splatSceneDownloadAndBuildPromise = null, this.splatSceneRemovalPromise = null, this.loadingSpinner = new Ei(null, this.rootElement || document.body), this.loadingSpinner.hide(), this.loadingProgressBar = new Mi(this.rootElement || document.body), this.loadingProgressBar.hide(), this.usingExternalCamera = !(!this.dropInMode && !this.camera), this.usingExternalRenderer = !(!this.dropInMode && !this.renderer), this.initialized = false, this.disposing = false, this.disposed = false, this.disposePromise = null, this.lastTime = 0, this.gaussianSplatCount = 0, this.totalFrames = 0, this.frame = 0, this.avatarMesh = null, this.skinModel = null, this.boneRoot = null, this.baseMesh = null, this.setSkinAttibutes = false, this.dropInMode || this.init();
|
|
43266
43648
|
}
|
|
43267
43649
|
createSplatMesh() {
|
|
43268
|
-
this.splatMesh = new ws(this.splatRenderMode, this.dynamicScene, this.enableOptionalEffects, this.halfPrecisionCovariancesOnGPU, this.devicePixelRatio, this.gpuAcceleratedSort, this.integerBasedSort, this.antialiased, this.maxScreenSpaceSplatSize, this.logLevel, this.sphericalHarmonicsDegree, this.sceneFadeInRateMultiplier, this.kernel2DSize
|
|
43650
|
+
this.splatMesh = new ws(this.splatRenderMode, this.dynamicScene, this.enableOptionalEffects, this.halfPrecisionCovariancesOnGPU, this.devicePixelRatio, this.gpuAcceleratedSort, this.integerBasedSort, this.antialiased, this.maxScreenSpaceSplatSize, this.logLevel, this.sphericalHarmonicsDegree, this.sceneFadeInRateMultiplier, this.kernel2DSize, this.irisOcclusionConfig), this.splatMesh.frustumCulled = false, this.onSplatMeshChangedCallback && this.onSplatMeshChangedCallback();
|
|
43269
43651
|
}
|
|
43270
43652
|
init() {
|
|
43271
43653
|
this.initialized || (this.rootElement || (this.usingExternalRenderer ? this.rootElement = this.renderer.domElement || document.body : (this.rootElement = document.createElement("div"), this.rootElement.style.width = "100%", this.rootElement.style.height = "100%", this.rootElement.style.position = "absolute", document.body.appendChild(this.rootElement))), this.setupCamera(), this.setupRenderer(), this.setupEventHandlers(), this.threeScene = this.threeScene || new Scene(), this.sceneHelper = new Ri(this.threeScene), this.sceneHelper.setupMeshCursor(), this.sceneHelper.setupFocusMarker(), this.sceneHelper.setupControlPlane(), this.loadingProgressBar.setContainer(this.rootElement), this.loadingSpinner.setContainer(this.rootElement), this.initialized = true);
|
|
@@ -43667,19 +44049,26 @@ class GaussianSplatRenderer {
|
|
|
43667
44049
|
Fi.debug("Found model folder in ZIP", { fileName: c }), Fi.debug("Creating GaussianSplatRenderer instance");
|
|
43668
44050
|
const h = new GaussianSplatRenderer(e, l), d = Rt.set(Pi ?? 0, ki ?? 0, _i ?? 1), u = new Vector3(Li ?? 0, Ui ?? 0, Hi ?? 0);
|
|
43669
44051
|
Fi.debug("Camera setup", { position: { x: d.x, y: d.y, z: d.z }, rotation: { x: u.x, y: u.y, z: u.z } });
|
|
43670
|
-
let p
|
|
44052
|
+
let p = 16777215;
|
|
43671
44053
|
try {
|
|
43672
44054
|
if (Oi) {
|
|
43673
44055
|
const e2 = parseInt(Oi, 16);
|
|
43674
|
-
isNaN(e2) ? Fi.warn("Invalid backgroundColor in config, using default", { value: Oi }) :
|
|
44056
|
+
isNaN(e2) ? Fi.warn("Invalid backgroundColor in config, using default", { value: Oi }) : p = e2;
|
|
43675
44057
|
}
|
|
43676
|
-
i?.backgroundColor && (h.isHexColorStrict(i.backgroundColor) ?
|
|
44058
|
+
i?.backgroundColor && (h.isHexColorStrict(i.backgroundColor) ? p = parseInt(i.backgroundColor, 16) : Fi.warn("Invalid backgroundColor option, using config value", { value: i.backgroundColor }));
|
|
43677
44059
|
} catch (e2) {
|
|
43678
44060
|
Fi.warn("Error parsing backgroundColor, using default", e2);
|
|
43679
44061
|
}
|
|
43680
|
-
Fi.debug("Background color set", { backgroundColor:
|
|
44062
|
+
Fi.debug("Background color set", { backgroundColor: p.toString(16) }), h.getChatState = i?.getChatState, h.getExpressionData = i?.getExpressionData, Fi.debug("Checking for iris_occlusion.json");
|
|
44063
|
+
let m, g = null;
|
|
44064
|
+
try {
|
|
44065
|
+
g = await h._loadJsonFromZip(c + "/iris_occlusion.json"), g ? (Fi.info("Iris occlusion configuration loaded", { rightIrisRanges: g.right_iris?.length ?? 0, leftIrisRanges: g.left_iris?.length ?? 0 }), h.irisOcclusionConfig = g) : Fi.debug("No iris_occlusion.json found, iris occlusion will be disabled");
|
|
44066
|
+
} catch (e2) {
|
|
44067
|
+
Fi.warn("Failed to load iris_occlusion.json, continuing without it", { error: e2.message }), h.irisOcclusionConfig = null;
|
|
44068
|
+
}
|
|
44069
|
+
Fi.debug("Creating Viewer instance");
|
|
43681
44070
|
try {
|
|
43682
|
-
h.viewer = new Viewer({ rootElement: e, threejsCanvas: h._canvas, cameraUp: [0, 1, 0], initialCameraPosition: [d.x, d.y, d.z], initialCameraRotation: [u.x, u.y, u.z], sphericalHarmonicsDegree: 0, backgroundColor:
|
|
44071
|
+
h.viewer = new Viewer({ rootElement: e, threejsCanvas: h._canvas, cameraUp: [0, 1, 0], initialCameraPosition: [d.x, d.y, d.z], initialCameraRotation: [u.x, u.y, u.z], sphericalHarmonicsDegree: 0, backgroundColor: p, sceneRevealMode: $.Default, sceneFadeInRateMultiplier: 3, irisOcclusionConfig: g });
|
|
43683
44072
|
} catch (e2) {
|
|
43684
44073
|
throw new Te(`Failed to create Viewer instance: ${e2.message}`, e2);
|
|
43685
44074
|
}
|
|
@@ -43696,17 +44085,10 @@ class GaussianSplatRenderer {
|
|
|
43696
44085
|
}
|
|
43697
44086
|
Fi.debug("Loading offset PLY file");
|
|
43698
44087
|
try {
|
|
43699
|
-
|
|
44088
|
+
m = await h.unpackFileAsBlob(c + "/offset.ply");
|
|
43700
44089
|
} catch (e2) {
|
|
43701
44090
|
throw new we(`Failed to load offset.ply: ${e2.message}`, c + "/offset.ply", e2);
|
|
43702
44091
|
}
|
|
43703
|
-
Fi.debug("Checking for iris_occlusion.json");
|
|
43704
|
-
let g = null;
|
|
43705
|
-
try {
|
|
43706
|
-
g = await h._loadJsonFromZip(c + "/iris_occlusion.json"), g ? (Fi.info("Iris occlusion configuration loaded", { rightIrisRanges: g.right_iris?.length ?? 0, leftIrisRanges: g.left_iris?.length ?? 0 }), h.irisOcclusionConfig = g, h.viewer.irisOcclusionConfig = g) : Fi.debug("No iris_occlusion.json found, iris occlusion will be disabled");
|
|
43707
|
-
} catch (e2) {
|
|
43708
|
-
Fi.warn("Failed to load iris_occlusion.json, continuing without it", { error: e2.message }), h.irisOcclusionConfig = null;
|
|
43709
|
-
}
|
|
43710
44092
|
if (i.loadProgress) try {
|
|
43711
44093
|
i.loadProgress(0.3);
|
|
43712
44094
|
} catch (e2) {
|
|
@@ -43714,7 +44096,7 @@ class GaussianSplatRenderer {
|
|
|
43714
44096
|
}
|
|
43715
44097
|
Fi.debug("Adding splat scene");
|
|
43716
44098
|
try {
|
|
43717
|
-
await h.viewer.addSplatScene(
|
|
44099
|
+
await h.viewer.addSplatScene(m, { progressiveLoad: true, sharedMemoryForWorkers: false, showLoadingUI: false, format: J.Ply });
|
|
43718
44100
|
} catch (e2) {
|
|
43719
44101
|
throw new Te(`Failed to add splat scene: ${e2.message}`, e2);
|
|
43720
44102
|
}
|