@xmov/avatar 2.0.0-alpha.38

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.
Files changed (160) hide show
  1. package/README.md +139 -0
  2. package/dist/agent/__tests__/agent.test.d.ts +1 -0
  3. package/dist/agent/audio-debug.d.ts +3 -0
  4. package/dist/agent/audio-uplink.d.ts +62 -0
  5. package/dist/agent/avatar.cjs +2 -0
  6. package/dist/agent/avatar.cjs.map +1 -0
  7. package/dist/agent/avatar.modern.js +2 -0
  8. package/dist/agent/avatar.modern.js.map +1 -0
  9. package/dist/agent/avatar.module.js +2 -0
  10. package/dist/agent/avatar.module.js.map +1 -0
  11. package/dist/agent/avatar.umd.js +2 -0
  12. package/dist/agent/avatar.umd.js.map +1 -0
  13. package/dist/agent/e2e-client.d.ts +32 -0
  14. package/dist/agent/fixed-audio-track.d.ts +48 -0
  15. package/dist/agent/index.cjs +5 -0
  16. package/dist/agent/index.d.ts +104 -0
  17. package/dist/agent/index.umd.js +10 -0
  18. package/dist/agent/microphone.d.ts +49 -0
  19. package/dist/agent/types.d.ts +153 -0
  20. package/dist/baseRender/AudioRenderer.d.ts +46 -0
  21. package/dist/baseRender/AudioWorklet.d.ts +78 -0
  22. package/dist/baseRender/AvatarRenderer.d.ts +226 -0
  23. package/dist/baseRender/MSEAudioPlayer.d.ts +51 -0
  24. package/dist/baseRender/UIRenderer.d.ts +39 -0
  25. package/dist/baseRender/pcm-audio-processor.d.ts +63 -0
  26. package/dist/control/APIForwarder.d.ts +23 -0
  27. package/dist/control/DataCacheQueue.d.ts +69 -0
  28. package/dist/control/EventDispatcher.d.ts +35 -0
  29. package/dist/control/RenderScheduler.d.ts +162 -0
  30. package/dist/control/SaveAndDownload.d.ts +14 -0
  31. package/dist/control/StreamingClient.d.ts +33 -0
  32. package/dist/control/VideoPlayer.d.ts +47 -0
  33. package/dist/control/ttsa.d.ts +111 -0
  34. package/dist/encoding/media-recorder-encoder.d.ts +33 -0
  35. package/dist/encoding/pcm-webm-encoder.d.ts +13 -0
  36. package/dist/encoding/webcodec-opus-encoder.d.ts +41 -0
  37. package/dist/encoding/webm-muxer.d.ts +90 -0
  38. package/dist/index.cjs +2 -0
  39. package/dist/index.cjs.map +1 -0
  40. package/dist/index.d.ts +231 -0
  41. package/dist/index.modern.js +2 -0
  42. package/dist/index.modern.js.map +1 -0
  43. package/dist/index.module.js +2 -0
  44. package/dist/index.module.js.map +1 -0
  45. package/dist/index.umd.js +2 -0
  46. package/dist/index.umd.js.map +1 -0
  47. package/dist/modules/Composition.d.ts +28 -0
  48. package/dist/modules/ResourceManager.d.ts +295 -0
  49. package/dist/modules/TrackRenderer/base-track.d.ts +4 -0
  50. package/dist/modules/TrackRenderer/index.d.ts +12 -0
  51. package/dist/modules/TrackRenderer/render-implements.d.ts +19 -0
  52. package/dist/modules/TrackRenderer/track-pic.d.ts +8 -0
  53. package/dist/modules/TrackRenderer/track-subtitle.d.ts +8 -0
  54. package/dist/modules/cache-manager.d.ts +5 -0
  55. package/dist/modules/decoder.d.ts +84 -0
  56. package/dist/modules/error-handle.d.ts +17 -0
  57. package/dist/modules/network.d.ts +21 -0
  58. package/dist/proto/face_data_pb.d.ts +0 -0
  59. package/dist/types/capability.d.ts +15 -0
  60. package/dist/types/error.d.ts +70 -0
  61. package/dist/types/event.d.ts +25 -0
  62. package/dist/types/frame-data.d.ts +107 -0
  63. package/dist/types/index.d.ts +187 -0
  64. package/dist/types/render.d.ts +18 -0
  65. package/dist/utils/DataInterface.d.ts +113 -0
  66. package/dist/utils/GLDevice.d.ts +46 -0
  67. package/dist/utils/GLPipeline.d.ts +84 -0
  68. package/dist/utils/GLPipelineDebugTools.d.ts +5 -0
  69. package/dist/utils/Math.d.ts +21 -0
  70. package/dist/utils/__tests__/capability-checker.test.d.ts +1 -0
  71. package/dist/utils/audio-session.d.ts +11 -0
  72. package/dist/utils/audio.d.ts +1 -0
  73. package/dist/utils/capability-checker.d.ts +23 -0
  74. package/dist/utils/encodeToken.d.ts +7 -0
  75. package/dist/utils/face.d.ts +1 -0
  76. package/dist/utils/float32-decoder.d.ts +26 -0
  77. package/dist/utils/fpsTracker.d.ts +27 -0
  78. package/dist/utils/index.d.ts +22 -0
  79. package/dist/utils/logger.d.ts +9 -0
  80. package/dist/utils/media-recorder-timestamp.d.ts +15 -0
  81. package/dist/utils/pcmAnalyzer.d.ts +40 -0
  82. package/dist/utils/perfermance.d.ts +15 -0
  83. package/dist/utils/request.d.ts +19 -0
  84. package/dist/utils/requestAnimateFrames.d.ts +31 -0
  85. package/dist/utils/time.d.ts +9 -0
  86. package/dist/view/DebugOverlay.d.ts +54 -0
  87. package/dist/worker/streaming-video.d.ts +1 -0
  88. package/package.json +82 -0
  89. package/src/agent/__tests__/agent.test.ts +3787 -0
  90. package/src/agent/audio-debug.ts +36 -0
  91. package/src/agent/audio-uplink.ts +392 -0
  92. package/src/agent/e2e-client.ts +215 -0
  93. package/src/agent/fixed-audio-track.ts +547 -0
  94. package/src/agent/index.ts +1393 -0
  95. package/src/agent/microphone.ts +534 -0
  96. package/src/agent/types.ts +197 -0
  97. package/src/baseRender/AudioRenderer.ts +317 -0
  98. package/src/baseRender/AudioWorklet.ts +776 -0
  99. package/src/baseRender/AvatarRenderer.ts +1559 -0
  100. package/src/baseRender/MSEAudioPlayer.ts +243 -0
  101. package/src/baseRender/UIRenderer.ts +195 -0
  102. package/src/baseRender/pcm-audio-processor.js +267 -0
  103. package/src/control/APIForwarder.ts +67 -0
  104. package/src/control/DataCacheQueue.ts +372 -0
  105. package/src/control/EventDispatcher.ts +106 -0
  106. package/src/control/RenderScheduler.ts +945 -0
  107. package/src/control/SaveAndDownload.js +77 -0
  108. package/src/control/StreamingClient.ts +138 -0
  109. package/src/control/VideoPlayer.ts +306 -0
  110. package/src/control/ttsa.ts +676 -0
  111. package/src/encoding/media-recorder-encoder.ts +175 -0
  112. package/src/encoding/pcm-webm-encoder.ts +72 -0
  113. package/src/encoding/webcodec-opus-encoder.ts +258 -0
  114. package/src/encoding/webm-muxer.ts +337 -0
  115. package/src/global.d.ts +105 -0
  116. package/src/index.ts +1920 -0
  117. package/src/modules/Composition.ts +79 -0
  118. package/src/modules/ResourceManager.ts +899 -0
  119. package/src/modules/TrackRenderer/base-track.ts +7 -0
  120. package/src/modules/TrackRenderer/index.ts +40 -0
  121. package/src/modules/TrackRenderer/render-implements.ts +269 -0
  122. package/src/modules/TrackRenderer/track-pic.ts +19 -0
  123. package/src/modules/TrackRenderer/track-subtitle.ts +16 -0
  124. package/src/modules/cache-manager.ts +10 -0
  125. package/src/modules/decoder.ts +515 -0
  126. package/src/modules/error-handle.ts +20 -0
  127. package/src/modules/network.ts +71 -0
  128. package/src/proto/face_data.proto +45 -0
  129. package/src/proto/face_data_pb.js +1297 -0
  130. package/src/proto/protobuf.min.js +8 -0
  131. package/src/types/capability.ts +23 -0
  132. package/src/types/error.ts +211 -0
  133. package/src/types/event.ts +38 -0
  134. package/src/types/frame-data.ts +121 -0
  135. package/src/types/index.ts +207 -0
  136. package/src/types/render.ts +18 -0
  137. package/src/utils/DataInterface.ts +746 -0
  138. package/src/utils/GLDevice.ts +286 -0
  139. package/src/utils/GLPipeline.ts +1293 -0
  140. package/src/utils/GLPipelineDebugTools.ts +174 -0
  141. package/src/utils/Math.ts +266 -0
  142. package/src/utils/__tests__/capability-checker.test.ts +159 -0
  143. package/src/utils/audio-session.ts +144 -0
  144. package/src/utils/audio.ts +44 -0
  145. package/src/utils/blueimp-md5.d.ts +3 -0
  146. package/src/utils/capability-checker.ts +379 -0
  147. package/src/utils/encodeToken.ts +100 -0
  148. package/src/utils/face.ts +29 -0
  149. package/src/utils/float32-decoder.js +130 -0
  150. package/src/utils/fpsTracker.ts +100 -0
  151. package/src/utils/index.ts +182 -0
  152. package/src/utils/logger.js +36 -0
  153. package/src/utils/media-recorder-timestamp.ts +79 -0
  154. package/src/utils/pcmAnalyzer.ts +154 -0
  155. package/src/utils/perfermance.ts +141 -0
  156. package/src/utils/request.ts +95 -0
  157. package/src/utils/requestAnimateFrames.ts +195 -0
  158. package/src/utils/time.ts +32 -0
  159. package/src/view/DebugOverlay.ts +404 -0
  160. package/src/worker/streaming-video.ts +4647 -0
@@ -0,0 +1,404 @@
1
+ import { EErrorCode, type SDKError } from "../types/error";
2
+ import { type ISessionResponse } from "../modules/ResourceManager";
3
+ import { formatTimestamp } from "../utils/time";
4
+
5
+ /**
6
+ * 调试信息浮层(支持复制会话ID + 禁止疯狂刷新DOM)
7
+ */
8
+ export class DebugOverlay {
9
+ private container: HTMLDivElement | null = null;
10
+ private sdkContainer: HTMLElement | null = null;
11
+ private sdk: any;
12
+ private sessionInfo: ISessionResponse;
13
+ private startTime: number;
14
+ private updateInterval: number | null = null;
15
+
16
+ private errors: SDKError[] = [];
17
+ private fps: number = 0;
18
+ private frameCount: number = 0;
19
+ private lastFpsUpdate: number = performance.now();
20
+
21
+ private videoInfo: { name: string; body_id: number; id: number } = {
22
+ name: "",
23
+ body_id: 0,
24
+ id: 0,
25
+ };
26
+ private audioInfo: { sf: number; ef: number; ad: Uint8Array } = {
27
+ sf: 0,
28
+ ef: 0,
29
+ ad: new Uint8Array(),
30
+ };
31
+ private eventInfo: { sf: number; ef: number; event: any[] } = {
32
+ sf: 0,
33
+ ef: 0,
34
+ event: [],
35
+ };
36
+
37
+ private memoryInfo: {
38
+ jsHeapSizeLimit?: number;
39
+ totalJSHeapSize?: number;
40
+ usedJSHeapSize?: number;
41
+ } = {};
42
+
43
+ private perfMetrics: Record<
44
+ string,
45
+ { times: number[]; avg: number; last: number; count: number }
46
+ > = {};
47
+ private currentFrameIndex: number = 0;
48
+
49
+ private domRefs: Record<string, HTMLElement> = {};
50
+
51
+ constructor(sdk: any, sessionInfo: ISessionResponse) {
52
+ this.sdk = sdk;
53
+ this.sdkContainer = sdk.el;
54
+ this.sessionInfo = sessionInfo;
55
+ this.startTime = Date.now();
56
+ this.trackFPS();
57
+ this.trackMemory();
58
+ }
59
+
60
+ setAudioInfo(info: { sf: number; ef: number; ad: Uint8Array }) {
61
+ this.audioInfo = info;
62
+ }
63
+
64
+ setEventData(info: { sf: number; ef: number; event: any[] }) {
65
+ this.eventInfo = info;
66
+ }
67
+
68
+ addError(error: SDKError) {
69
+ this.errors.unshift(error);
70
+ if (this.errors.length >= 20) this.errors.pop();
71
+ }
72
+
73
+ public show(): void {
74
+ if (this.container) {
75
+ this.container.style.display = "block";
76
+ return;
77
+ }
78
+ this.createOverlay();
79
+ this.updateInterval = window.setInterval(() => this.updateTextOnly(), 200);
80
+ this.updateTextOnly();
81
+ }
82
+
83
+ public hide(): void {
84
+ if (this.container) this.container.style.display = "none";
85
+ if (this.updateInterval) {
86
+ clearInterval(this.updateInterval);
87
+ this.updateInterval = null;
88
+ }
89
+ }
90
+
91
+ public destroy(): void {
92
+ this.hide();
93
+ if (this.container?.parentNode) {
94
+ this.container.parentNode.removeChild(this.container);
95
+ }
96
+ this.container = null;
97
+ }
98
+
99
+ setVideoInfo(info: any) {
100
+ this.videoInfo = info;
101
+ }
102
+
103
+ private updateTextOnly(): void {
104
+ if (!this.container) return;
105
+
106
+ this.sessionInfo.session_id = this.sdk.resourceManager.session_id;
107
+ this.currentFrameIndex =
108
+ this.sdk.renderScheduler.frameAnimationController?.getCurrentFrame() ?? 0;
109
+
110
+ this.updatePerfMetrics();
111
+ this.updateMemory();
112
+
113
+ this.setText("look_name", this.sessionInfo.config.look_name);
114
+ this.setText("video_name", `${this.videoInfo.name} - ${this.videoInfo.body_id}`);
115
+ this.setText("session_id", this.sessionInfo.session_id);
116
+ this.setText("frame_idx", String(this.currentFrameIndex));
117
+ this.setText(
118
+ "audio_info",
119
+ `开始帧: ${this.audioInfo.sf} / 结束帧: ${this.audioInfo.ef} / 长度: ${this.audioInfo.ad.length}`
120
+ );
121
+ this.setText(
122
+ "event_info",
123
+ `开始帧: ${this.eventInfo.sf} / 结束帧: ${this.eventInfo.ef} / 长度: ${this.eventInfo.event.length}`
124
+ );
125
+
126
+ this.setText("perf", this.formatPerfMetrics());
127
+ this.setText("fps", String(this.fps));
128
+ this.setText("mem", this.formatMemory());
129
+
130
+ const duration = ((Date.now() - this.startTime) / 1000).toFixed(1);
131
+ this.setText("session_start", formatTimestamp(this.sessionInfo.session_start_time));
132
+ this.setText("session_duration", `${duration}s`);
133
+ this.setText("network", this.getNetworkInfo());
134
+
135
+ this.setText("error_count", `(${this.errors.length})`);
136
+ this.setText("error_log", this.formatErrors(false));
137
+ this.setText("ttsa_error_log", this.formatErrors(true));
138
+ }
139
+
140
+ private setText(id: string, text: string) {
141
+ const el = this.domRefs[id];
142
+ if (el) el.innerHTML = text;
143
+ }
144
+
145
+ // 🔥 复制会话 ID 功能
146
+ private copySessionId() {
147
+ const sessionId = this.sessionInfo.session_id;
148
+ if (!sessionId) return;
149
+ navigator.clipboard.writeText(sessionId).then(() => {
150
+ }).catch(err => {
151
+ console.warn("复制失败", err);
152
+ });
153
+ }
154
+
155
+ private createOverlay(): void {
156
+ this.container = document.createElement("div");
157
+ this.container.id = "sdk-debug-overlay";
158
+ this.applyStyles(this.container);
159
+ this.container.style.userSelect = "text";
160
+
161
+ const make = (id: string, label: string, value = "") => {
162
+ const d = document.createElement("div");
163
+ d.style.marginBottom = "8px";
164
+ d.innerHTML = `<span style="color:#888;font-size:11px;">${label}:</span>`;
165
+ const v = document.createElement("span");
166
+ v.style.color = "#fff";
167
+ v.style.fontSize = "11px";
168
+ v.style.marginLeft = "4px";
169
+ v.textContent = value;
170
+ d.append(v);
171
+ this.domRefs[id] = v;
172
+ return d;
173
+ };
174
+
175
+ // ==========================
176
+ // 🔥 带复制按钮的会话 ID
177
+ // ==========================
178
+ const makeSessionIdWithCopy = () => {
179
+ const d = document.createElement("div");
180
+ d.style.marginBottom = "8px";
181
+ d.style.display = "flex";
182
+ d.style.alignItems = "center";
183
+ d.style.gap = "6px";
184
+
185
+ const label = document.createElement("span");
186
+ label.style.color = "#888";
187
+ label.style.fontSize = "11px";
188
+ label.textContent = "会话 ID:";
189
+
190
+ const value = document.createElement("span");
191
+ value.style.color = "#fff";
192
+ value.style.fontSize = "11px";
193
+ value.style.wordBreak = "break-all";
194
+ value.style.flex = "1";
195
+ this.domRefs["session_id"] = value;
196
+
197
+ const copyBtn = document.createElement("button");
198
+ copyBtn.textContent = "复制";
199
+ copyBtn.style.fontSize = "10px";
200
+ copyBtn.style.padding = "2px 6px";
201
+ copyBtn.style.borderRadius = "3px";
202
+ copyBtn.style.border = "none";
203
+ copyBtn.style.background = "#007aff";
204
+ copyBtn.style.color = "#fff";
205
+ copyBtn.style.cursor = "pointer";
206
+ copyBtn.onclick = () => this.copySessionId();
207
+
208
+ d.append(label, value, copyBtn);
209
+ return d;
210
+ };
211
+
212
+ const title = document.createElement("h3");
213
+ title.textContent = "🔍 调试信息";
214
+ title.style.margin = "0 0 12px 0";
215
+ title.style.color = "#00ff88";
216
+ title.style.fontSize = "14px";
217
+
218
+ const content = document.createElement("div");
219
+ content.append(
220
+ title,
221
+ make("look_name", "角色look_name"),
222
+ make("video_name", "当前视频"),
223
+ makeSessionIdWithCopy(), // 🔥 替换成带复制按钮的
224
+ make("frame_idx", "当前帧"),
225
+ make("audio_info", "下发音频"),
226
+ make("event_info", "下发事件"),
227
+ make("fps", "FPS"),
228
+ );
229
+
230
+ const perfTitle = document.createElement("div");
231
+ perfTitle.style.marginBottom = "8px";
232
+ perfTitle.innerHTML = `<span style="color:#888;font-size:11px;">性能耗时:</span>`;
233
+ const perf = document.createElement("div");
234
+ perf.style.fontSize = "11px";
235
+ perf.style.background = "rgba(0,0,0,0.2)";
236
+ perf.style.padding = "4px";
237
+ perf.style.borderRadius = "4px";
238
+ perf.style.color = "#fff";
239
+ this.domRefs["perf"] = perf;
240
+ content.append(perfTitle, perf);
241
+
242
+ const errWrap = document.createElement("div");
243
+ errWrap.style.borderTop = "1px solid #444";
244
+ errWrap.style.paddingTop = "8px";
245
+ errWrap.style.marginBottom = "8px";
246
+
247
+ const errTitle = document.createElement("h4");
248
+ errTitle.style.margin = "0 0 6px 0";
249
+ errTitle.style.fontSize = "12px";
250
+ errTitle.style.color = "#888";
251
+ const errCount = document.createElement("span");
252
+ this.domRefs["error_count"] = errCount;
253
+ errTitle.append("✅ 错误日志 ", errCount);
254
+ errWrap.append(errTitle);
255
+
256
+ const makePre = (id: string) => {
257
+ const p = document.createElement("pre");
258
+ p.style.margin = "0 0 4px 0";
259
+ p.style.padding = "6px";
260
+ p.style.background = "rgba(255,255,255,0.05)";
261
+ p.style.borderRadius = "4px";
262
+ p.style.fontSize = "10px";
263
+ p.style.lineHeight = "1.3";
264
+ p.style.color = "#888";
265
+ p.style.maxHeight = "120px";
266
+ p.style.overflowY = "auto";
267
+ p.style.whiteSpace = "pre-wrap";
268
+ this.domRefs[id] = p;
269
+ return p;
270
+ };
271
+
272
+ const errLog = makePre("error_log");
273
+ const ttsaLog = makePre("ttsa_error_log");
274
+ errWrap.append(errLog, ttsaLog);
275
+ content.append(errWrap);
276
+
277
+ content.append(
278
+ make("mem", "内存占用"),
279
+ make("session_start", "会话开始时间"),
280
+ make("session_duration", "会话时长"),
281
+ make("network", "网络信息")
282
+ );
283
+
284
+ this.container.append(content);
285
+ this.sdkContainer?.appendChild(this.container);
286
+ this.enableDrag(this.container);
287
+ }
288
+
289
+ private formatErrors(isTtsa: boolean): string {
290
+ let list = this.errors;
291
+ if (isTtsa) list = list.filter(e => e.code === EErrorCode.TTSA_ERROR);
292
+ else list = list.filter(e => e.code !== EErrorCode.TTSA_ERROR);
293
+ if (list.length === 0) return "暂无错误";
294
+ return list
295
+ .map(e => `[${new Date(e.timestamp).toLocaleTimeString()}] ${e.code}: ${e.message}`)
296
+ .join("\n");
297
+ }
298
+
299
+ private updatePerfMetrics() {
300
+ if (!(window as any).performanceTracker) return;
301
+ const raw = (window as any).performanceTracker.getVideoMetrics();
302
+ for (const [k, v] of Object.entries(raw)) {
303
+ const t = (v as any)?.time ?? 0;
304
+ if (!this.perfMetrics[k]) this.perfMetrics[k] = { times: [], avg: 0, last: 0, count: 0 };
305
+ const m = this.perfMetrics[k];
306
+ m.times.push(t);
307
+ m.last = t;
308
+ m.count++;
309
+ m.avg = m.times.reduce((a, b) => a + b, 0) / m.times.length;
310
+ }
311
+ }
312
+
313
+ private formatPerfMetrics(): string {
314
+ const m = this.perfMetrics;
315
+ if (!Object.keys(m).length) return "暂无数据";
316
+ return Object.entries(m)
317
+ .map(([k, v]) => `${k}: 最近:${v.last.toFixed(2)}ms 平均:${v.avg.toFixed(2)}ms 次数:${v.count}`)
318
+ .join("<br>");
319
+ }
320
+
321
+ private formatMemory(): string {
322
+ const m = this.memoryInfo;
323
+ if (!m.usedJSHeapSize) return "N/A";
324
+ const used = (m.usedJSHeapSize! / 1048576).toFixed(1);
325
+ const total = (m.totalJSHeapSize! / 1048576).toFixed(1);
326
+ const limit = (m.jsHeapSizeLimit! / 1048576).toFixed(1);
327
+ return `${used}MB / ${total}MB / ${limit}MB`;
328
+ }
329
+
330
+ private trackFPS() {
331
+ const loop = (now: number) => {
332
+ this.frameCount++;
333
+ if (now - this.lastFpsUpdate >= 1000) {
334
+ this.fps = this.frameCount;
335
+ this.frameCount = 0;
336
+ this.lastFpsUpdate = now;
337
+ }
338
+ requestAnimationFrame(loop);
339
+ };
340
+ requestAnimationFrame(loop);
341
+ }
342
+
343
+ private updateMemory() {
344
+ if ((window as any).performance?.memory) {
345
+ const m = (window as any).performance.memory;
346
+ this.memoryInfo = {
347
+ jsHeapSizeLimit: m.jsHeapSizeLimit,
348
+ totalJSHeapSize: m.totalJSHeapSize,
349
+ usedJSHeapSize: m.usedJSHeapSize,
350
+ };
351
+ }
352
+ }
353
+
354
+ private trackMemory() {
355
+ if ((window as any).performance?.memory) {
356
+ setInterval(() => this.updateMemory(), 1000);
357
+ }
358
+ }
359
+
360
+ private getNetworkInfo(): string {
361
+ const conn = (navigator as any).connection;
362
+ if (!conn) return "N/A";
363
+ return `类型:${conn.effectiveType} 下行:${conn.downlink}Mbps 延迟:${conn.rtt}ms`;
364
+ }
365
+
366
+ private applyStyles(el: HTMLElement) {
367
+ el.style.position = "fixed";
368
+ el.style.bottom = "50px";
369
+ el.style.right = "10px";
370
+ el.style.width = "350px";
371
+ el.style.maxHeight = "400px";
372
+ el.style.overflowY = "auto";
373
+ el.style.background = "rgba(0,0,0,0.8)";
374
+ el.style.borderRadius = "8px";
375
+ el.style.padding = "10px";
376
+ el.style.zIndex = "99999";
377
+ el.style.fontSize = "12px";
378
+ el.style.color = "#fff";
379
+ el.style.textAlign="left";
380
+ }
381
+
382
+ private enableDrag(el: HTMLElement) {
383
+ let dragging = false, startX = 0, startY = 0, left = 0, top = 0;
384
+ el.style.cursor = "move";
385
+ el.addEventListener("mousedown", e => {
386
+ dragging = true;
387
+ startX = e.clientX;
388
+ startY = e.clientY;
389
+ left = el.offsetLeft;
390
+ top = el.offsetTop;
391
+ (document.body as any).style.userSelect = "none";
392
+ });
393
+ document.addEventListener("mousemove", e => {
394
+ if (!dragging) return;
395
+ el.style.left = left + e.clientX - startX + "px";
396
+ el.style.top = top + e.clientY - startY + "px";
397
+ el.style.right = el.style.bottom = "unset";
398
+ });
399
+ document.addEventListener("mouseup", () => {
400
+ dragging = false;
401
+ (document.body as any).style.userSelect = "";
402
+ });
403
+ }
404
+ }